diff options
Diffstat (limited to 'host')
370 files changed, 57468 insertions, 0 deletions
diff --git a/host/.gitignore b/host/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/host/.gitignore @@ -0,0 +1 @@ +/build diff --git a/host/AUTHORS.txt b/host/AUTHORS.txt new file mode 100644 index 000000000..44b7516cd --- /dev/null +++ b/host/AUTHORS.txt @@ -0,0 +1,40 @@ +Matt Ettus - matt@ettus.com + USRP1 FPGA code + USRP2/N200 FPGA code + USRP-E100 FPGA code + +Josh Blum - josh@ettus.com + driver framework + USRP2/N200 firmware + USRP2/N200 host code + USRP-E100 host code + Basic/LF host code + XCVR2450 host code + RFX Series host code + +Jason Abele - jason@ettus.com + RFX Series host code + WBX host code + DBSRX host code + DBSRX2 host code + +Eric Blossom - eb@comsec.com + USRP1 firmware + USRP2 firmware + +Tom Tsou - ttsou@vt.edu + UHD-USB framework + LIBUSB host code + USRP1 host code + USRP1 firmware + +Nick Foster - nick@ettus.com + LIBUSB host code + USRP1 host code + TVRX host code + USRP-N200 firmware + USRP-N200 host code + +Philip Balister - philip@opensdr.com + USRP-E100 kernel module + USRP-E100 utilities diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt new file mode 100644 index 000000000..f14c0bd7c --- /dev/null +++ b/host/CMakeLists.txt @@ -0,0 +1,277 @@ +# +# Copyright 2010-2012 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +#IF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) +# MESSAGE(FATAL_ERROR "Prevented in-tree built. This is bad practice.") +#ENDIF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + +######################################################################## +# Project setup +######################################################################## +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(UHD CXX) +ENABLE_TESTING() +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) +INCLUDE(UHDComponent) #enable components +INCLUDE(UHDPackage) #setup cpack + +######################################################################## +# Install Dirs +######################################################################## +#when the library suffix should be 64 (applies to redhat linux family) +IF(NOT DEFINED LIB_SUFFIX AND REDHAT AND CMAKE_SYSTEM_PROCESSOR MATCHES "64$") + SET(LIB_SUFFIX 64) +ENDIF() +SET(LIB_SUFFIX ${LIB_SUFFIX} CACHE STRING "lib directory suffix") +SET(RUNTIME_DIR bin) +SET(LIBRARY_DIR lib${LIB_SUFFIX}) +SET(INCLUDE_DIR include) +SET(PKG_DATA_DIR share/uhd) +IF(NOT DEFINED PKG_LIB_DIR) + SET(PKG_LIB_DIR ${PKG_DATA_DIR}) +ENDIF() +SET(PKG_DOC_DIR share/doc/uhd) + +######################################################################## +# Local Include Dir +######################################################################## +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) + +######################################################################## +# Optional Compiler Flags +######################################################################## +INCLUDE(CheckCXXCompilerFlag) +MACRO(UHD_ADD_OPTIONAL_CXX_COMPILER_FLAG flag have) + CHECK_CXX_COMPILER_FLAG(${flag} ${have}) + IF(${have}) + ADD_DEFINITIONS(${flag}) + ENDIF(${have}) +ENDMACRO(UHD_ADD_OPTIONAL_CXX_COMPILER_FLAG) + +#select the release build type by default to get optimization flags +IF(NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE "Release") + MESSAGE(STATUS "Build type not specified: defaulting to release.") +ENDIF(NOT CMAKE_BUILD_TYPE) +SET(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "") + +IF(CMAKE_COMPILER_IS_GNUCXX) + ADD_DEFINITIONS(-Wall) + ADD_DEFINITIONS(-Wextra) + ADD_DEFINITIONS(-Wsign-compare) + #ADD_DEFINITIONS(-Wconversion) + #ADD_DEFINITIONS(-pedantic) + #ADD_DEFINITIONS(-ansi) + IF(NOT WIN32) + #only export symbols that are declared to be part of the uhd api (non dll platforms) + UHD_ADD_OPTIONAL_CXX_COMPILER_FLAG(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN) + UHD_ADD_OPTIONAL_CXX_COMPILER_FLAG(-fvisibility-inlines-hidden HAVE_VISIBILITY_INLINES_HIDDEN) + ENDIF(NOT WIN32) +ENDIF(CMAKE_COMPILER_IS_GNUCXX) + +IF(MSVC) + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/cmake/msvc) + ADD_DEFINITIONS( #stop all kinds of compatibility warnings + -D_SCL_SECURE_NO_WARNINGS + -D_SCL_SECURE_NO_DEPRECATE + -D_CRT_SECURE_NO_WARNINGS + -D_CRT_SECURE_NO_DEPRECATE + -D_CRT_NONSTDC_NO_WARNINGS + -D_CRT_NONSTDC_NO_DEPRECATE + ) +ENDIF(MSVC) + +IF(CYGWIN) + ADD_DEFINITIONS(-D__USE_W32_SOCKETS) #boost asio says we need this +ENDIF(CYGWIN) + +IF(WIN32) + ADD_DEFINITIONS(-D_WIN32_WINNT=0x0501) #minimum version required is windows xp + ADD_DEFINITIONS(-DNOMINMAX) #disables stupidity and enables std::min and std::max +ENDIF(WIN32) + +######################################################################## +# Setup Boost +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring Boost C++ Libraries...") +SET(BOOST_REQUIRED_COMPONENTS + date_time + filesystem + program_options + regex + system + thread + unit_test_framework +) + +IF(UNIX AND EXISTS "/usr/lib64") + LIST(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix +ENDIF(UNIX AND EXISTS "/usr/lib64") + +IF(MSVC) + SET(BOOST_ALL_DYN_LINK "${BOOST_ALL_DYN_LINK}" CACHE BOOL "boost enable dynamic linking") + IF(BOOST_ALL_DYN_LINK) + ADD_DEFINITIONS(-DBOOST_ALL_DYN_LINK) #setup boost auto-linking in msvc + ELSE(BOOST_ALL_DYN_LINK) + UNSET(BOOST_REQUIRED_COMPONENTS) #empty components list for static link + ENDIF(BOOST_ALL_DYN_LINK) +ENDIF(MSVC) + +SET(Boost_ADDITIONAL_VERSIONS + "1.35.0" "1.35" "1.36.0" "1.36" "1.37.0" "1.37" "1.38.0" "1.38" "1.39.0" "1.39" + "1.40.0" "1.40" "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44" + "1.45.0" "1.45" "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.49.0" "1.49" + "1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54" + "1.55.0" "1.55" "1.56.0" "1.56" "1.57.0" "1.57" "1.58.0" "1.58" "1.59.0" "1.59" + "1.60.0" "1.60" "1.61.0" "1.61" "1.62.0" "1.62" "1.63.0" "1.63" "1.64.0" "1.64" + "1.65.0" "1.65" "1.66.0" "1.66" "1.67.0" "1.67" "1.68.0" "1.68" "1.69.0" "1.69" +) +FIND_PACKAGE(Boost 1.36 COMPONENTS ${BOOST_REQUIRED_COMPONENTS}) + +INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) +LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) + +MESSAGE(STATUS "Boost include directories: ${Boost_INCLUDE_DIRS}") +MESSAGE(STATUS "Boost library directories: ${Boost_LIBRARY_DIRS}") +MESSAGE(STATUS "Boost libraries: ${Boost_LIBRARIES}") + +######################################################################## +# Check Python Modules +######################################################################## +INCLUDE(UHDPython) + +PYTHON_CHECK_MODULE( + "Python version 2.6 or greater" + "platform" "platform.python_version() >= '2.6'" + HAVE_PYTHON_PLAT_MIN_VERSION +) + +PYTHON_CHECK_MODULE( + "Cheetah templates 2.0.0 or greater" + "Cheetah" "Cheetah.Version >= '2.0.0'" + HAVE_PYTHON_MODULE_CHEETAH +) + +######################################################################## +# Create Uninstall Target +######################################################################## +CONFIGURE_FILE( + ${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake +@ONLY) + +ADD_CUSTOM_TARGET(uninstall + ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake +) + +######################################################################## +# Install Package Docs +######################################################################## +INSTALL(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/README.txt + ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt + ${CMAKE_CURRENT_SOURCE_DIR}/AUTHORS.txt + DESTINATION ${PKG_DOC_DIR} + COMPONENT readme +) + +######################################################################## +# Register top level components +######################################################################## +LIBUHD_REGISTER_COMPONENT("LibUHD" ENABLE_LIBUHD ON "Boost_FOUND;HAVE_PYTHON_PLAT_MIN_VERSION;HAVE_PYTHON_MODULE_CHEETAH" OFF) +LIBUHD_REGISTER_COMPONENT("Examples" ENABLE_EXAMPLES ON "ENABLE_LIBUHD" OFF) +LIBUHD_REGISTER_COMPONENT("Utils" ENABLE_UTILS ON "ENABLE_LIBUHD" OFF) +LIBUHD_REGISTER_COMPONENT("Tests" ENABLE_TESTS ON "ENABLE_LIBUHD" OFF) + +######################################################################## +# Add the subdirectories +######################################################################## +ADD_SUBDIRECTORY(docs) + +IF(ENABLE_EXAMPLES) + ADD_SUBDIRECTORY(examples) +ENDIF(ENABLE_EXAMPLES) + +ADD_SUBDIRECTORY(include) + +IF(ENABLE_LIBUHD) + ADD_SUBDIRECTORY(lib) +ENDIF(ENABLE_LIBUHD) + +IF(ENABLE_TESTS) + ADD_SUBDIRECTORY(tests) +ENDIF(ENABLE_TESTS) + +IF(ENABLE_UTILS) + ADD_SUBDIRECTORY(utils) +ENDIF(ENABLE_UTILS) + +ADD_SUBDIRECTORY(usrp_e_utils) + +######################################################################## +# Create Pkg Config File +######################################################################## +FOREACH(inc ${Boost_INCLUDE_DIRS}) + LIST(APPEND UHD_PC_CFLAGS "-I${inc}") +ENDFOREACH(inc) + +FOREACH(lib ${Boost_LIBRARY_DIRS}) + LIST(APPEND UHD_PC_LIBS "-L${lib}") +ENDFOREACH(lib) + +#use space-separation format for the pc file +STRING(REPLACE ";" " " UHD_PC_REQUIRES "${UHD_PC_REQUIRES}") +STRING(REPLACE ";" " " UHD_PC_CFLAGS "${UHD_PC_CFLAGS}") +STRING(REPLACE ";" " " UHD_PC_LIBS "${UHD_PC_LIBS}") + +#unset these vars to avoid hard-coded paths to cross environment +IF(CMAKE_CROSSCOMPILING) + UNSET(UHD_PC_CFLAGS) + UNSET(UHD_PC_LIBS) +ENDIF(CMAKE_CROSSCOMPILING) + +CONFIGURE_FILE( + ${CMAKE_CURRENT_SOURCE_DIR}/uhd.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/uhd.pc +@ONLY) + +INSTALL( + FILES ${CMAKE_CURRENT_BINARY_DIR}/uhd.pc + DESTINATION ${LIBRARY_DIR}/pkgconfig + COMPONENT libraries +) + +######################################################################## +# Handle pre-built images +######################################################################## +IF(DEFINED UHD_IMAGES_DIR AND EXISTS "${UHD_IMAGES_DIR}") + FILE(GLOB _image_files "${UHD_IMAGES_DIR}/*.*") + MESSAGE(STATUS "Using images:") + FOREACH(_img ${_image_files}) + MESSAGE(STATUS " ${_img}") + ENDFOREACH(_img) + INSTALL(FILES ${_image_files} DESTINATION ${PKG_DATA_DIR}/images COMPONENT images) +ENDIF(DEFINED UHD_IMAGES_DIR AND EXISTS "${UHD_IMAGES_DIR}") + +######################################################################## +# Print Summary +######################################################################## +UHD_PRINT_COMPONENT_SUMMARY() +MESSAGE(STATUS "Building version: ${UHD_VERSION}") +MESSAGE(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") diff --git a/host/LICENSE.txt b/host/LICENSE.txt new file mode 100644 index 000000000..9aa03b39b --- /dev/null +++ b/host/LICENSE.txt @@ -0,0 +1,12 @@ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/host/README.txt b/host/README.txt new file mode 100644 index 000000000..473e0ab17 --- /dev/null +++ b/host/README.txt @@ -0,0 +1,41 @@ +############################################### +# Ettus Research - USRP Hardware Driver +############################################### +The hardware driver for Ettus Research products. + +############################################### +# Supported USRP Motherboards +############################################### +USRP1 +USRP2 +N200 +N210 +E100 +E110 +B100 + +############################################### +# Supported USRP Daughterboards +############################################### +Basic RX +Basic TX +LF RX +LF TX +RFX Series +XCVR 2450 +WBX + Simple GDB +DBSRX +DBSRX2 +TVRX +TVRX2 +SBX + +############################################### +# Documentation +############################################### +Online documentation available at: +http://code.ettus.com/redmine/ettus/projects/uhd/wiki + +The build system can generate the html for the manual and Doxygen. +Docutils and Doxygen are required to build the html docs. +See the docs directory for the manual source (reStructuredText). diff --git a/host/apps/omap_debug/.gitignore b/host/apps/omap_debug/.gitignore new file mode 100644 index 000000000..008a23138 --- /dev/null +++ b/host/apps/omap_debug/.gitignore @@ -0,0 +1,20 @@ +.gitignore +clkgen-config +fpga-downloader +usrp-e-button +usrp-e-crc-rw +usrp-e-ctl +usrp-e-debug-pins +usrp-e-fpga-rw +usrp-e-gpio +usrp-e-i2c +usrp-e-lb-test +usrp-e-led +usrp-e-loopback +usrp-e-random-loopback +usrp-e-rw +usrp-e-spi +usrp-e-timed +usrp-e-uart +usrp-e-uart-rx +usrp-e-mm-loopback diff --git a/host/apps/omap_debug/Makefile b/host/apps/omap_debug/Makefile new file mode 100644 index 000000000..f8b9f2bd9 --- /dev/null +++ b/host/apps/omap_debug/Makefile @@ -0,0 +1,33 @@ +CFLAGS=-Wall -I../../lib/usrp/usrp_e/ -march=armv7-a -mtune=cortex-a8 -mfpu=neon -O3 +CXXFLAGS=-Wall -I../../lib/usrp/usrp_e/ -march=armv7-a -mtune=cortex-a8 -mfpu=neon -O3 + +all : usrp-e-spi usrp-e-i2c usrp-e-uart usrp-e-led usrp-e-ctl usrp-e-button usrp-e-uart-rx usrp-e-gpio usrp-e-debug-pins + +usrp-e-spi : usrp-e-spi.c + +usrp-e-i2c : usrp-e-i2c.c + +usrp-e-uart : usrp-e-uart.c + +usrp-e-uart-rx : usrp-e-uart-rx.c + +usrp-e-led : usrp-e-led.c + +usrp-e-ctl : usrp-e-ctl.c + +usrp-e-button : usrp-e-button.c + +usrp-e-gpio : usrp-e-gpio.c + +usrp-e-debug-pins : usrp-e-debug-pins.c +clean : + rm -f usrp-e-spi + rm -f usrp-e-i2c + rm -f usrp-e-uart + rm -f usrp-e-uart-rx + rm -f usrp-e-led + rm -f usrp-e-ctl + rm -f usrp-e-button + rm -f usrp-e-gpio + rm -f usrp-e-debug-pins + rm -f usrp-e-lb-test diff --git a/host/apps/omap_debug/README b/host/apps/omap_debug/README new file mode 100644 index 000000000..bbe0c2cc4 --- /dev/null +++ b/host/apps/omap_debug/README @@ -0,0 +1 @@ +OMAP development tools go here diff --git a/host/apps/omap_debug/set_debug_pins.py b/host/apps/omap_debug/set_debug_pins.py new file mode 100755 index 000000000..0f9ecd7b9 --- /dev/null +++ b/host/apps/omap_debug/set_debug_pins.py @@ -0,0 +1,35 @@ +#!/usr/bin/python + +import os + +# Memory Map +misc_base = 0 +uart_base = 1 +spi_base = 2 +i2c_base = 3 +gpio_base = 4 * 128 +settings_base = 5 + +# GPIO offset +gpio_pins = 0 +gpio_ddr = 4 +gpio_ctrl_lo = 8 +gpio_ctrl_hi = 12 + +def set_reg(reg, val): + os.system("./usrp1-e-ctl w %d 1 %d" % (reg,val)) + +def get_reg(reg): + fin,fout = os.popen4("./usrp1-e-ctl r %d 1" % (reg,)) + print fout.read() + +# Set DDRs to output +set_reg(gpio_base+gpio_ddr, 0xFFFF) +set_reg(gpio_base+gpio_ddr+2, 0xFFFF) + +# Set CTRL to Debug #0 ( A is for debug 0, F is for debug 1 ) +set_reg(gpio_base+gpio_ctrl_lo, 0xAAAA) +set_reg(gpio_base+gpio_ctrl_lo+2, 0xAAAA) +set_reg(gpio_base+gpio_ctrl_hi, 0xAAAA) +set_reg(gpio_base+gpio_ctrl_hi+2, 0xAAAA) + diff --git a/host/apps/omap_debug/test.c b/host/apps/omap_debug/test.c new file mode 100644 index 000000000..36f4d700a --- /dev/null +++ b/host/apps/omap_debug/test.c @@ -0,0 +1,34 @@ +#include <stdio.h> + +void +main() +{ + int x; + char *y; + long long z; + + x = 0x01020304; + z = 0x0102030405060708LL; + + printf("%x\n",x); + y = (char *)&x; + printf("%x\n",y[0]); + printf("%x\n",y[1]); + printf("%x\n",y[2]); + printf("%x\n",y[3]); + + printf("Printing z ...\n"); + printf("%llx\n",z); + printf("Printing z done\n"); + + y = (char *)&z; + printf("%x\n",y[0]); + printf("%x\n",y[1]); + printf("%x\n",y[2]); + printf("%x\n",y[3]); + printf("%x\n",y[4]); + printf("%x\n",y[5]); + printf("%x\n",y[6]); + printf("%x\n",y[7]); +} + diff --git a/host/apps/omap_debug/u1e-read-stream.c b/host/apps/omap_debug/u1e-read-stream.c new file mode 100644 index 000000000..4e4c21d9e --- /dev/null +++ b/host/apps/omap_debug/u1e-read-stream.c @@ -0,0 +1,21 @@ +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +int main(int rgc, char *argv[]) +{ + int fp, cnt, n; + short buf[1024]; + + n = 0; + + fp = open("/dev/usrp1_e0", O_RDONLY); + printf("fp = %d\n", fp); + + do { + cnt = read(fp, buf, 2048); + n++; +// printf("Bytes read - %d\n", cnt); + } while(n < 10*512); + printf("Data - %hX\n", buf[0]); +} diff --git a/host/apps/omap_debug/usrp-e-button.c b/host/apps/omap_debug/usrp-e-button.c new file mode 100644 index 000000000..f13291491 --- /dev/null +++ b/host/apps/omap_debug/usrp-e-button.c @@ -0,0 +1,56 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include "usrp_e.h" +#include "usrp_e_regs.hpp" + +// Usage: usrp_e_uart <string> + +#define PB1 (1<<8) +#define PB2 (1<<9) +#define PB3 (1<<10) +#define P1 (0) +#define P2 (0xFF) +#define P3 (0xAA) +#define P4 (0x55) + +int main(int argc, char *argv[]) +{ + int fp, ret; + struct usrp_e_ctl16 d; + int pb1=0, pb2=0, pb3=0, p1=0, p2=0, p3=0, p4=0; + + fp = open("/dev/usrp_e0", O_RDWR); + printf("fp = %d\n", fp); + + d.offset = UE_REG_MISC_SW; + d.count = 1; + + do { + ret = ioctl(fp, USRP_E_READ_CTL16, &d); + if (d.buf[0] & PB1) { + pb1 = 1; + printf("Pushbutton 1 hit\n"); + } + + if (d.buf[0] & PB2) { + pb2 = 1; + printf("Pushbutton 2 hit\n"); + } + + if (d.buf[0] & PB3) { + pb3 = 1; + printf("Pushbutton 3 hit\n"); + } + + sleep(1); + + } while (!(pb1 && pb2 && pb3)); + + return 0; +} diff --git a/host/apps/omap_debug/usrp-e-ctl.c b/host/apps/omap_debug/usrp-e-ctl.c new file mode 100644 index 000000000..69c48ee6f --- /dev/null +++ b/host/apps/omap_debug/usrp-e-ctl.c @@ -0,0 +1,48 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#include "usrp_e.h" + +// Usage: usrp_e_ctl w|r offset number_of_values val1 val2 .... + +int main(int argc, char *argv[]) +{ + int fp, i, cnt, ret; + struct usrp_e_ctl16 ctl_data; + + if (argc < 4) { + printf("Usage: usrp_e_ctl w|r offset number_of_values val1 val2 ....\n"); + exit(-1); + } + + cnt = atoi(argv[3]); + + ctl_data.offset = atoi(argv[2]); + ctl_data.count = cnt; + + fp = open("/dev/usrp_e0", O_RDWR); + printf("fp = %d\n", fp); + + if (*argv[1] == 'w') { + for (i=0; i<cnt; i++) + ctl_data.buf[i] = atoi(argv[4+i]); + + ret = ioctl(fp, USRP_E_WRITE_CTL16, &ctl_data); + printf("Return value from write ioctl = %d\n", ret); + } + + if (*argv[1] == 'r') { + ret = ioctl(fp, USRP_E_READ_CTL16, &ctl_data); + printf("Return value from write ioctl = %d\n", ret); + + for (i=0; i<ctl_data.count; i++) { + if (!(i%8)) + printf("\nData at %4d :", i); + printf(" %5d", ctl_data.buf[i]); + } + printf("\n"); + } +} diff --git a/host/apps/omap_debug/usrp-e-debug-pins.c b/host/apps/omap_debug/usrp-e-debug-pins.c new file mode 100644 index 000000000..d18bbf990 --- /dev/null +++ b/host/apps/omap_debug/usrp-e-debug-pins.c @@ -0,0 +1,77 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "usrp_e.h" +#include "usrp_e_regs.hpp" + +// Usage: usrp_e_gpio <string> + +static int fp; + +static int read_reg(__u16 reg) +{ + int ret; + struct usrp_e_ctl16 d; + + d.offset = reg; + d.count = 1; + ret = ioctl(fp, USRP_E_READ_CTL16, &d); + return d.buf[0]; +} + +static void write_reg(__u16 reg, __u16 val) +{ + int ret; + struct usrp_e_ctl16 d; + + d.offset = reg; + d.count = 1; + d.buf[0] = val; + ret = ioctl(fp, USRP_E_WRITE_CTL16, &d); +} + +int main(int argc, char *argv[]) +{ + int test; + + test = 0; + if (argc < 2) { + printf("%s 0|1|off\n", argv[0]); + } + + fp = open("/dev/usrp_e0", O_RDWR); + printf("fp = %d\n", fp); + if (fp < 0) { + perror("Open failed"); + return -1; + } + + if (strcmp(argv[1], "0") == 0) { + printf("Selected 0 based on %s\n", argv[1]); + write_reg(UE_REG_GPIO_TX_DDR, 0xFFFF); + write_reg(UE_REG_GPIO_RX_DDR, 0xFFFF); + write_reg(UE_REG_GPIO_TX_SEL, 0x0); + write_reg(UE_REG_GPIO_RX_SEL, 0x0); + write_reg(UE_REG_GPIO_TX_DBG, 0xFFFF); + write_reg(UE_REG_GPIO_RX_DBG, 0xFFFF); + } else if (strcmp(argv[1], "1") == 0) { + printf("Selected 1 based on %s\n", argv[1]); + write_reg(UE_REG_GPIO_TX_DDR, 0xFFFF); + write_reg(UE_REG_GPIO_RX_DDR, 0xFFFF); + write_reg(UE_REG_GPIO_TX_SEL, 0xFFFF); + write_reg(UE_REG_GPIO_RX_SEL, 0xFFFF); + write_reg(UE_REG_GPIO_TX_DBG, 0xFFFF); + write_reg(UE_REG_GPIO_RX_DBG, 0xFFFF); + } else { + printf("Selected off based on %s\n", argv[1]); + write_reg(UE_REG_GPIO_TX_DDR, 0x0); + write_reg(UE_REG_GPIO_RX_DDR, 0x0); + } + + return 0; +} diff --git a/host/apps/omap_debug/usrp-e-i2c.c b/host/apps/omap_debug/usrp-e-i2c.c new file mode 100644 index 000000000..da8709ae1 --- /dev/null +++ b/host/apps/omap_debug/usrp-e-i2c.c @@ -0,0 +1,87 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#include "usrp_e.h" + +// Usage: usrp_e_i2c w address data0 data1 data 2 .... +// Usage: usrp_e_i2c r address count + +int main(int argc, char *argv[]) +{ + int fp, ret, i, tmp; + struct usrp_e_i2c *i2c_msg; + int direction, address, count; + + if (argc < 3) { + printf("Usage: usrp-e-i2c w address data0 data1 data2 ...\n"); + printf("Usage: usrp-e-i2c r address count\n"); + printf("All addresses and data in hex.\n"); + exit(-1); + } + + if (strcmp(argv[1], "r") == 0) { + direction = 0; + } else if (strcmp(argv[1], "w") == 0) { + direction = 1; + } else { + return -1; + } + + sscanf(argv[2], "%X", &address); + printf("Address = %X\n", address); + + fp = open("/dev/usrp_e0", O_RDWR); + printf("fp = %d\n", fp); + if (fp < 0) { + perror("Open failed"); + return -1; + } + +// sleep(1); + + if (direction) { + count = argc - 3; + } else { + sscanf(argv[3], "%X", &count); + } + printf("Count = %X\n", count); + + i2c_msg = malloc(sizeof(i2c_msg) + count * sizeof(char)); + + i2c_msg->addr = address; + i2c_msg->len = count; + + for (i = 0; i < count; i++) { + i2c_msg->data[i] = i; + } + + if (direction) { + // Write + + for (i=0; i<count; i++) { + sscanf(argv[3+i], "%X", &tmp); + i2c_msg->data[i] = tmp; + } + + ret = ioctl(fp, USRP_E_I2C_WRITE, i2c_msg); + printf("Return value from i2c_write ioctl: %d\n", ret); + } else { + // Read + + ret = ioctl(fp, USRP_E_I2C_READ, i2c_msg); + printf("Return value from i2c_read ioctl: %d\n", ret); + + printf("Ioctl: %d Data read :", ret); + for (i=0; i<count; i++) { + printf(" %X", i2c_msg->data[i]); + } + printf("\n"); + + } + return 0; +} diff --git a/host/apps/omap_debug/usrp-e-lb-test.c b/host/apps/omap_debug/usrp-e-lb-test.c new file mode 100644 index 000000000..68848064e --- /dev/null +++ b/host/apps/omap_debug/usrp-e-lb-test.c @@ -0,0 +1,58 @@ +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdlib.h> +#include <unistd.h> +#include <stddef.h> +#include "usrp_e.h" + +// max length #define PKT_DATA_LENGTH 1016 + +int main(int argc, char *argv[]) +{ + struct usrp_transfer_frame *tx_data, *rx_data; + int i, fp, packet_data_length, cnt; + struct usrp_e_ctl16 d; + + if (argc < 2) { + printf("%s data_size (in bytes < 2040)\n", argv[0]); + return -1; + } + + packet_data_length = atoi(argv[1]); + + fp = open("/dev/usrp_e0", O_RDWR); + + d.offset = 14; + d.count = 1; + d.buf[0] = (1 << 13); + ioctl(fp, USRP_E_WRITE_CTL16, &d); + + tx_data = malloc(2048); + rx_data = malloc(2048); + + tx_data->status = 0; + tx_data->len = sizeof(struct usrp_transfer_frame) + packet_data_length; + + while (1) { + + for (i = 0; i < packet_data_length; i++) { + tx_data->buf[i] = random() >> 24; + + } + + cnt = write(fp, tx_data, 2048); + cnt = read(fp, rx_data, 2048); + + if (tx_data->len != rx_data->len) + printf("Bad frame length sent %d, read %d\n", tx_data->len, rx_data->len); + + for (i = 0; i < packet_data_length; i++) { + if (tx_data->buf[i] != rx_data->buf[i]) + printf("Bad data at %d, sent %d, received %d\n", i, tx_data->buf[i], rx_data->buf[i]); + } + printf("---------------------------------------------------\n"); + sleep(1); + } +} diff --git a/host/apps/omap_debug/usrp-e-led.c b/host/apps/omap_debug/usrp-e-led.c new file mode 100644 index 000000000..d1b6c8996 --- /dev/null +++ b/host/apps/omap_debug/usrp-e-led.c @@ -0,0 +1,35 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include "usrp_e.h" +#include "usrp_e_regs.hpp" + +// Usage: usrp_e_uart <string> + + +int main(int argc, char *argv[]) +{ + int fp, i, ret; + struct usrp_e_ctl16 d; + + fp = open("/dev/usrp_e0", O_RDWR); + printf("fp = %d\n", fp); + + d.offset = UE_REG_MISC_BASE; + d.count = 1; + + while (1) { + for (i=0; i<8; i++) { + d.buf[0] = i; + ret = ioctl(fp, USRP_E_WRITE_CTL16, &d); + sleep(1); + } + } + + return 0; +} diff --git a/host/apps/omap_debug/usrp-e-ram.c b/host/apps/omap_debug/usrp-e-ram.c new file mode 100644 index 000000000..d548f7ccd --- /dev/null +++ b/host/apps/omap_debug/usrp-e-ram.c @@ -0,0 +1,25 @@ +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +int main(int rgc, char *argv[]) +{ + int fp, i, cnt; + unsigned short buf[1024]; + unsigned short buf_rb[1024]; + + fp = open("/dev/usrp1_e0", O_RDWR); + printf("fp = %d\n", fp); + + for (i=0; i<1024; i++) + buf[i] = i*256; + write(fp, buf, 2048); + read(fp, buf_rb, 2048); + + printf("Read back %hX %hX\n", buf_rb[0], buf_rb[1]); + + for (i=0; i<1024; i++) { + if (buf[i] != buf_rb[i]) + printf("Read - %hX, expected - %hX\n", buf_rb[i], buf[i]); + } +} diff --git a/host/apps/omap_debug/usrp-e-read.c b/host/apps/omap_debug/usrp-e-read.c new file mode 100644 index 000000000..c28f018d5 --- /dev/null +++ b/host/apps/omap_debug/usrp-e-read.c @@ -0,0 +1,18 @@ +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +int main(int rgc, char *argv[]) +{ + int fp, cnt; + short buf[1024]; + + fp = open("/dev/usrp1_e0", O_RDONLY); + printf("fp = %d\n", fp); + + do { + cnt = read(fp, buf, 2048); +// printf("Bytes read - %d\n", cnt); + } while(1); + printf("Data - %hX\n", buf[0]); +} diff --git a/host/apps/omap_debug/usrp-e-spi.c b/host/apps/omap_debug/usrp-e-spi.c new file mode 100644 index 000000000..c353c409b --- /dev/null +++ b/host/apps/omap_debug/usrp-e-spi.c @@ -0,0 +1,54 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#include "usrp_e.h" + +// Usage: usrp_e_spi w|rb slave data + +int main(int argc, char *argv[]) +{ + int fp, slave, length, ret; + unsigned int data; + struct usrp_e_spi spi_dat; + + if (argc < 5) { + printf("Usage: usrp_e_spi w|rb slave transfer_length data\n"); + exit(-1); + } + + slave = atoi(argv[2]); + length = atoi(argv[3]); + data = atoll(argv[4]); + + printf("Data = %X\n", data); + + fp = open("/dev/usrp_e0", O_RDWR); + printf("fp = %d\n", fp); + if (fp < 0) { + perror("Open failed"); + return -1; + } + +// sleep(1); + + + spi_dat.slave = slave; + spi_dat.data = data; + spi_dat.length = length; + spi_dat.flags = UE_SPI_PUSH_FALL | UE_SPI_LATCH_RISE; + + if (*argv[1] == 'r') { + spi_dat.readback = 1; + ret = ioctl(fp, USRP_E_SPI, &spi_dat); + printf("Ioctl returns: %d, Data returned = %d\n", ret, spi_dat.data); + } else { + spi_dat.readback = 0; + ioctl(fp, USRP_E_SPI, &spi_dat); + } + + return 0; +} diff --git a/host/apps/omap_debug/usrp-e-uart-rx.c b/host/apps/omap_debug/usrp-e-uart-rx.c new file mode 100644 index 000000000..24b417980 --- /dev/null +++ b/host/apps/omap_debug/usrp-e-uart-rx.c @@ -0,0 +1,53 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "usrp_e.h" +#include "usrp_e_regs.hpp" + +// Usage: usrp_e_uart <string> + + +int main(int argc, char *argv[]) +{ + int fp, ret; + struct usrp_e_ctl16 d; + __u16 clkdiv; + + if (argc == 0) { + printf("Usage: usrp-e-uart-rx <opt clkdiv>\n"); + printf("clkdiv = 278 is 230.4k \n"); + printf("clkdiv = 556 is 115.2k \n"); + exit(-1); + } + + fp = open("/dev/usrp_e0", O_RDWR); + printf("fp = %d\n", fp); + + if (argc == 2) { + clkdiv = atoi(argv[1]); + d.offset = UE_REG_UART_CLKDIV; + d.count = 1; + d.buf[0] = clkdiv; + ret = ioctl(fp, USRP_E_WRITE_CTL16, &d); + } + + while(1) { + d.offset = UE_REG_UART_RXLEVEL; + d.count = 1; + ret = ioctl(fp, USRP_E_READ_CTL16, &d); + + if (d.buf[0] > 0) { + d.offset = UE_REG_UART_RXCHAR; + d.count = 1; + ret = ioctl(fp, USRP_E_READ_CTL16, &d); + printf("%c", d.buf[0]); + fflush(stdout); + } + } + + return 0; +} diff --git a/host/apps/omap_debug/usrp-e-uart.c b/host/apps/omap_debug/usrp-e-uart.c new file mode 100644 index 000000000..2956c407f --- /dev/null +++ b/host/apps/omap_debug/usrp-e-uart.c @@ -0,0 +1,48 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "usrp_e.h" +#include "usrp_e_regs.hpp" + +// Usage: usrp_e_uart <string> + + +int main(int argc, char *argv[]) +{ + int fp, i, ret; + struct usrp_e_ctl16 d; + char *str = argv[1]; + __u16 clkdiv; + + if (argc < 2) { + printf("Usage: usrp_e_uart <string> <opt clkdiv>\n"); + printf("clkdiv = 278 is 230.4k \n"); + printf("clkdiv = 556 is 115.2k \n"); + exit(-1); + } + + fp = open("/dev/usrp_e0", O_RDWR); + printf("fp = %d\n", fp); + + if (argc == 3) { + clkdiv = atoi(argv[2]); + d.offset = UE_REG_UART_CLKDIV; + d.count = 1; + d.buf[0] = clkdiv; + ret = ioctl(fp, USRP_E_WRITE_CTL16, &d); + } + + for (i=0; i<strlen(str); i++) { + d.offset = UE_REG_UART_TXCHAR; + d.count = 1; + d.buf[0] = str[i]; + ret = ioctl(fp, USRP_E_WRITE_CTL16, &d); + printf("Wrote %X, to %X, ret = %d\n", d.buf[0], d.offset, ret); + } + + return 0; +} diff --git a/host/apps/omap_debug/usrp-e-write.c b/host/apps/omap_debug/usrp-e-write.c new file mode 100644 index 000000000..903c0071f --- /dev/null +++ b/host/apps/omap_debug/usrp-e-write.c @@ -0,0 +1,21 @@ +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +int main(int rgc, char *argv[]) +{ + int fp, i, cnt; + short buf[1024]; + + fp = open("/dev/usrp1_e0", O_WRONLY); + printf("fp = %d\n", fp); + + for (i=0; i<1024; i++) { + buf[i] = i; + } + +// do { + cnt = write(fp, buf, 2048); + printf("Bytes written - %d\n", cnt); +// } while (1); +} diff --git a/host/apps/omap_debug/usrp_e.h b/host/apps/omap_debug/usrp_e.h new file mode 100644 index 000000000..2c4aa2ac1 --- /dev/null +++ b/host/apps/omap_debug/usrp_e.h @@ -0,0 +1,60 @@ + +/* + * Copyright (C) 2010 Ettus Research, LLC + * + * Written by Philip Balister <philip@opensdr.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __USRP_E_H +#define __USRP_E_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +struct usrp_e_ctl16 { + __u32 offset; + __u32 count; + __u16 buf[20]; +}; + +struct usrp_e_ctl32 { + __u32 offset; + __u32 count; + __u32 buf[10]; +}; + +#define USRP_E_IOC_MAGIC 'u' +#define USRP_E_WRITE_CTL16 _IOW(USRP_E_IOC_MAGIC, 0x20, struct usrp_e_ctl16) +#define USRP_E_READ_CTL16 _IOWR(USRP_E_IOC_MAGIC, 0x21, struct usrp_e_ctl16) +#define USRP_E_WRITE_CTL32 _IOW(USRP_E_IOC_MAGIC, 0x22, struct usrp_e_ctl32) +#define USRP_E_READ_CTL32 _IOWR(USRP_E_IOC_MAGIC, 0x23, struct usrp_e_ctl32) +#define USRP_E_GET_RB_INFO _IOR(USRP_E_IOC_MAGIC, 0x27, struct usrp_e_ring_buffer_size_t) +#define USRP_E_GET_COMPAT_NUMBER _IO(USRP_E_IOC_MAGIC, 0x28) + +#define USRP_E_COMPAT_NUMBER 2 + +/* Flag defines */ +#define RB_USER (1<<0) +#define RB_KERNEL (1<<1) +#define RB_OVERRUN (1<<2) +#define RB_DMA_ACTIVE (1<<3) +#define RB_USER_PROCESS (1<<4) + +struct ring_buffer_info { + int flags; + int len; +}; + +struct usrp_e_ring_buffer_size_t { + int num_pages_rx_flags; + int num_rx_frames; + int num_pages_tx_flags; + int num_tx_frames; +}; + +#endif diff --git a/host/cmake/Modules/FindDocutils.cmake b/host/cmake/Modules/FindDocutils.cmake new file mode 100644 index 000000000..25d1349e2 --- /dev/null +++ b/host/cmake/Modules/FindDocutils.cmake @@ -0,0 +1,21 @@ +# +# Copyright 2011-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +FIND_PROGRAM(RST2HTML_EXECUTABLE NAMES rst2html rst2html.py) +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Docutils DEFAULT_MSG RST2HTML_EXECUTABLE) diff --git a/host/cmake/Modules/FindGit.cmake b/host/cmake/Modules/FindGit.cmake new file mode 100644 index 000000000..2d8214287 --- /dev/null +++ b/host/cmake/Modules/FindGit.cmake @@ -0,0 +1,46 @@ +# The module defines the following variables: +# GIT_EXECUTABLE - path to git command line client +# GIT_FOUND - true if the command line client was found +# Example usage: +# find_package(Git) +# if(GIT_FOUND) +# message("git found: ${GIT_EXECUTABLE}") +# endif() + +#============================================================================= +# Copyright 2010 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distributed this file outside of CMake, substitute the full +# License text for the above reference.) + +# Look for 'git' or 'eg' (easy git) +# +set(git_names git eg) + +# Prefer .cmd variants on Windows unless running in a Makefile +# in the MSYS shell. +# +if(WIN32) + if(NOT CMAKE_GENERATOR MATCHES "MSYS") + set(git_names git.cmd git eg.cmd eg) + endif() +endif() + +find_program(GIT_EXECUTABLE + NAMES ${git_names} + DOC "git command line client" + ) +mark_as_advanced(GIT_EXECUTABLE) + +# Handle the QUIETLY and REQUIRED arguments and set GIT_FOUND to TRUE if +# all listed variables are TRUE + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Git DEFAULT_MSG GIT_EXECUTABLE) diff --git a/host/cmake/Modules/FindUSB1.cmake b/host/cmake/Modules/FindUSB1.cmake new file mode 100644 index 000000000..8bfc2f975 --- /dev/null +++ b/host/cmake/Modules/FindUSB1.cmake @@ -0,0 +1,38 @@ + +#find libusb 1.0 on various systems +#sets LIBUSB_FOUND, LIBUSB_LIBRARIES, LIBUSB_INCLUDE_DIRS +#override LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIRS to manually set + +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(PC_LIBUSB QUIET libusb-1.0) + +FIND_PATH(LIBUSB_INCLUDE_DIRS + NAMES libusb.h + HINTS $ENV{LIBUSB_DIR}/include ${PC_LIBUSB_INCLUDEDIR} + PATHS /usr/local/include/libusb-1.0 /usr/local/include + /usr/include/libusb-1.0 /usr/include + /opt/local/include/libusb-1.0 + + #non-conforming naming convention, + #backwards compatible with old script + ${LIBUSB_INCLUDE_DIR} +) + +#standard library name for libusb-1.0 +set(libusb1_library_names usb-1.0) + +#libusb-1.0 compatible library on freebsd +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + list(APPEND libusb1_library_names usb) +endif() + +FIND_LIBRARY(LIBUSB_LIBRARIES + NAMES ${libusb1_library_names} + HINTS $ENV{LIBUSB_DIR}/lib ${PC_LIBUSB_LIBDIR} + PATHS /usr/local/lib /usr/lib /opt/local/lib +) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIRS) +MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIRS LIBUSB_LIBRARIES) + diff --git a/host/cmake/Modules/UHDComponent.cmake b/host/cmake/Modules/UHDComponent.cmake new file mode 100644 index 000000000..52b7450d5 --- /dev/null +++ b/host/cmake/Modules/UHDComponent.cmake @@ -0,0 +1,77 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +SET(_uhd_enabled_components "" CACHE INTERNAL "" FORCE) +SET(_uhd_disabled_components "" CACHE INTERNAL "" FORCE) + +######################################################################## +# Register a component into the system +# - name the component string name +# - var the global enable variable +# - enb the default enable setting +# - deps a list of dependencies +# - dis the default disable setting +######################################################################## +MACRO(LIBUHD_REGISTER_COMPONENT name var enb deps dis) + MESSAGE(STATUS "") + MESSAGE(STATUS "Configuring ${name} support...") + FOREACH(dep ${deps}) + MESSAGE(STATUS " Dependency ${dep} = ${${dep}}") + ENDFOREACH(dep) + + #setup the dependent option for this component + INCLUDE(CMakeDependentOption) + CMAKE_DEPENDENT_OPTION(${var} "enable ${name} support" ${enb} "${deps}" ${dis}) + + #append the component into one of the lists + IF(${var}) + MESSAGE(STATUS " Enabling ${name} support.") + LIST(APPEND _uhd_enabled_components ${name}) + ELSE(${var}) + MESSAGE(STATUS " Disabling ${name} support.") + LIST(APPEND _uhd_disabled_components ${name}) + ENDIF(${var}) + MESSAGE(STATUS " Override with -D${var}=ON/OFF") + + #make components lists into global variables + SET(_uhd_enabled_components ${_uhd_enabled_components} CACHE INTERNAL "" FORCE) + SET(_uhd_disabled_components ${_uhd_disabled_components} CACHE INTERNAL "" FORCE) +ENDMACRO(LIBUHD_REGISTER_COMPONENT) + +######################################################################## +# Print the registered component summary +######################################################################## +FUNCTION(UHD_PRINT_COMPONENT_SUMMARY) + MESSAGE(STATUS "") + MESSAGE(STATUS "######################################################") + MESSAGE(STATUS "# UHD enabled components ") + MESSAGE(STATUS "######################################################") + FOREACH(comp ${_uhd_enabled_components}) + MESSAGE(STATUS " * ${comp}") + ENDFOREACH(comp) + + MESSAGE(STATUS "") + MESSAGE(STATUS "######################################################") + MESSAGE(STATUS "# UHD disabled components ") + MESSAGE(STATUS "######################################################") + FOREACH(comp ${_uhd_disabled_components}) + MESSAGE(STATUS " * ${comp}") + ENDFOREACH(comp) + + MESSAGE(STATUS "") +ENDFUNCTION(UHD_PRINT_COMPONENT_SUMMARY) diff --git a/host/cmake/Modules/UHDPackage.cmake b/host/cmake/Modules/UHDPackage.cmake new file mode 100644 index 000000000..4dd072c37 --- /dev/null +++ b/host/cmake/Modules/UHDPackage.cmake @@ -0,0 +1,177 @@ +# +# Copyright 2010-2012 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +INCLUDE(UHDVersion) #sets version information + +SET(UHD_RELEASE_MODE "${UHD_RELEASE_MODE}" CACHE BOOL "set UHD to release mode to build installers") + +######################################################################## +# Setup additional defines for OS types +######################################################################## +IF(CMAKE_SYSTEM_NAME STREQUAL "Linux") + SET(LINUX TRUE) +ENDIF() + +IF(LINUX AND EXISTS "/etc/debian_version") + SET(DEBIAN TRUE) +ENDIF() + +IF(LINUX AND EXISTS "/etc/redhat-release") + SET(REDHAT TRUE) +ENDIF() + +######################################################################## +# Set generator type for recognized systems +######################################################################## +IF(CPACK_GENERATOR) + #already set +ELSEIF(APPLE) + SET(CPACK_GENERATOR PackageMaker) +ELSEIF(WIN32) + SET(CPACK_GENERATOR NSIS) +ELSEIF(DEBIAN) + SET(CPACK_GENERATOR DEB) +ELSEIF(REDHAT) + SET(CPACK_GENERATOR RPM) +ELSE() + SET(CPACK_GENERATOR TGZ) +ENDIF() + +######################################################################## +# Setup package file name +######################################################################## +FIND_PROGRAM(LSB_RELEASE_EXECUTABLE lsb_release) +IF((DEBIAN OR REDHAT) AND LSB_RELEASE_EXECUTABLE) + + #extract system information by executing the commands + EXECUTE_PROCESS( + COMMAND ${LSB_RELEASE_EXECUTABLE} --short --id + OUTPUT_VARIABLE LSB_ID OUTPUT_STRIP_TRAILING_WHITESPACE + ) + EXECUTE_PROCESS( + COMMAND ${LSB_RELEASE_EXECUTABLE} --short --release + OUTPUT_VARIABLE LSB_RELEASE OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + #set a more sensible package name for this system + SET(CPACK_PACKAGE_FILE_NAME "uhd_${UHD_VERSION}_${LSB_ID}-${LSB_RELEASE}-${CMAKE_SYSTEM_PROCESSOR}") + +ENDIF() + +IF(${CPACK_GENERATOR} STREQUAL NSIS) + SET(CPACK_PACKAGE_FILE_NAME "uhd_${UHD_VERSION}_win32") + SET(CPACK_PACKAGE_INSTALL_DIRECTORY "${CMAKE_PROJECT_NAME}") +ENDIF() + +######################################################################## +# Setup CPack General +######################################################################## +SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Ettus Research - USRP Hardware Driver") +SET(CPACK_PACKAGE_VENDOR "Ettus Research LLC") +SET(CPACK_PACKAGE_CONTACT "Ettus Research <support@ettus.com>") +SET(CPACK_PACKAGE_VERSION "${UHD_VERSION}") +SET(CPACK_RESOURCE_FILE_WELCOME ${CMAKE_SOURCE_DIR}/README.txt) +SET(CPACK_RESOURCE_FILE_README ${CMAKE_SOURCE_DIR}/AUTHORS.txt) +SET(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) + +######################################################################## +# Setup CPack Components +######################################################################## +SET(CPACK_COMPONENT_LIBRARIES_GROUP "Development") +SET(CPACK_COMPONENT_HEADERS_GROUP "Development") +SET(CPACK_COMPONENT_UTILITIES_GROUP "Runtime") +SET(CPACK_COMPONENT_EXAMPLES_GROUP "Runtime") +SET(CPACK_COMPONENT_TESTS_GROUP "Runtime") +SET(CPACK_COMPONENT_MANUAL_GROUP "Documentation") +SET(CPACK_COMPONENT_DOXYGEN_GROUP "Documentation") +SET(CPACK_COMPONENT_README_GROUP "Documentation") + +SET(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") +SET(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C++ Headers") +SET(CPACK_COMPONENT_UTILITIES_DISPLAY_NAME "Utilities") +SET(CPACK_COMPONENT_EXAMPLES_DISPLAY_NAME "Examples") +SET(CPACK_COMPONENT_TESTS_DISPLAY_NAME "Unit Tests") +SET(CPACK_COMPONENT_MANUAL_DISPLAY_NAME "Manual") +SET(CPACK_COMPONENT_DOXYGEN_DISPLAY_NAME "Doxygen") +SET(CPACK_COMPONENT_README_DISPLAY_NAME "Readme") +SET(CPACK_COMPONENT_IMAGES_DISPLAY_NAME "Images") + +SET(CPACK_COMPONENT_LIBRARIES_DESCRIPTION "Dynamic link library") +SET(CPACK_COMPONENT_HEADERS_DESCRIPTION "C++ development headers") +SET(CPACK_COMPONENT_UTILITIES_DESCRIPTION "Utility executables and python scripts") +SET(CPACK_COMPONENT_EXAMPLES_DESCRIPTION "Example executables") +SET(CPACK_COMPONENT_TESTS_DESCRIPTION "Unit test executables") +SET(CPACK_COMPONENT_MANUAL_DESCRIPTION "Manual/application notes (rst and html)") +SET(CPACK_COMPONENT_DOXYGEN_DESCRIPTION "API documentation (html)") +SET(CPACK_COMPONENT_README_DESCRIPTION "Readme files (txt)") +SET(CPACK_COMPONENT_IMAGES_DESCRIPTION "FPGA and firmware images") + +SET(CPACK_COMPONENT_README_REQUIRED TRUE) + +SET(CPACK_COMPONENT_UTILITIES_DEPENDS libraries) +SET(CPACK_COMPONENT_EXAMPLES_DEPENDS libraries) +SET(CPACK_COMPONENT_TESTS_DEPENDS libraries) + +SET(CPACK_COMPONENTS_ALL libraries headers utilities examples tests manual doxygen readme images) + +######################################################################## +# Setup CPack Debian +######################################################################## +SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libusb-1.0-0, libboost-dev") +SET(CPACK_DEBIAN_PACKAGE_RECOMMENDS "python, python-tk") +FOREACH(filename preinst postinst prerm postrm) + LIST(APPEND CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA ${CMAKE_BINARY_DIR}/debian/${filename}) + FILE(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/debian) + CONFIGURE_FILE( + ${CMAKE_SOURCE_DIR}/cmake/debian/${filename}.in + ${CMAKE_BINARY_DIR}/debian/${filename} + @ONLY) +ENDFOREACH(filename) + +######################################################################## +# Setup CPack RPM +######################################################################## +SET(CPACK_RPM_PACKAGE_REQUIRES "boost-devel, libusb1") + +FOREACH(filename post_install post_uninstall pre_install pre_uninstall) + STRING(TOUPPER ${filename} filename_upper) + LIST(APPEND CPACK_RPM_${filename_upper}_SCRIPT_FILE ${CMAKE_BINARY_DIR}/redhat/${filename}) + FILE(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/redhat) + CONFIGURE_FILE( + ${CMAKE_SOURCE_DIR}/cmake/redhat/${filename}.in + ${CMAKE_BINARY_DIR}/redhat/${filename} + @ONLY) +ENDFOREACH(filename) + +######################################################################## +# Setup CPack NSIS +######################################################################## +SET(CPACK_NSIS_MODIFY_PATH ON) + +SET(HLKM_ENV "\\\"SYSTEM\\\\CurrentControlSet\\\\Control\\\\Session Manager\\\\Environment\\\"") + +SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " + WriteRegStr HKLM ${HLKM_ENV} \\\"UHD_PKG_DATA_PATH\\\" \\\"$INSTDIR\\\\share\\\\uhd\\\" +") + +SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " + DeleteRegValue HKLM ${HLKM_ENV} \\\"UHD_PKG_DATA_PATH\\\" +") + +######################################################################## +INCLUDE(CPack) #include after setting vars diff --git a/host/cmake/Modules/UHDPython.cmake b/host/cmake/Modules/UHDPython.cmake new file mode 100644 index 000000000..fdcdccb4b --- /dev/null +++ b/host/cmake/Modules/UHDPython.cmake @@ -0,0 +1,86 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +IF(NOT DEFINED INCLUDED_UHD_PYTHON_CMAKE) +SET(INCLUDED_UHD_PYTHON_CMAKE TRUE) + +######################################################################## +# Setup Python +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring the python interpreter...") +#this allows the user to override PYTHON_EXECUTABLE +IF(PYTHON_EXECUTABLE) + + SET(PYTHONINTERP_FOUND TRUE) + +#otherwise if not set, try to automatically find it +ELSE(PYTHON_EXECUTABLE) + + #use the built-in find script + FIND_PACKAGE(PythonInterp) + + #and if that fails use the find program routine + IF(NOT PYTHONINTERP_FOUND) + FIND_PROGRAM(PYTHON_EXECUTABLE NAMES python python2.7 python2.6) + IF(PYTHON_EXECUTABLE) + SET(PYTHONINTERP_FOUND TRUE) + ENDIF(PYTHON_EXECUTABLE) + ENDIF(NOT PYTHONINTERP_FOUND) + +ENDIF(PYTHON_EXECUTABLE) + +#make the path to the executable appear in the cmake gui +SET(PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "python interpreter") + +MESSAGE(STATUS "Python interpreter: ${PYTHON_EXECUTABLE}") +MESSAGE(STATUS "Override with: -DPYTHON_EXECUTABLE=<path-to-python>") + +IF(NOT PYTHONINTERP_FOUND) + MESSAGE(FATAL_ERROR "Error: Python interpretor required by the build system.") +ENDIF(NOT PYTHONINTERP_FOUND) + +MACRO(PYTHON_CHECK_MODULE desc mod cmd have) + MESSAGE(STATUS "") + MESSAGE(STATUS "Python checking for ${desc}") + EXECUTE_PROCESS( + COMMAND ${PYTHON_EXECUTABLE} -c " +######################################### +try: import ${mod} +except: exit(1) +try: assert ${cmd} +except: exit(2) +exit(0) +#########################################" + RESULT_VARIABLE ${have} + ) + IF(${have} EQUAL 0) + MESSAGE(STATUS "Python checking for ${desc} - found") + SET(${have} TRUE) + ELSEIF(${have} EQUAL 1) + MESSAGE(STATUS "Python checking for ${desc} - \"import ${mod}\" failed") + SET(${have} FALSE) + ELSEIF(${have} EQUAL 2) + MESSAGE(STATUS "Python checking for ${desc} - \"assert ${cmd}\" failed") + SET(${have} FALSE) + ELSE() + MESSAGE(STATUS "Python checking for ${desc} - unknown error") + SET(${have} FALSE) + ENDIF() +ENDMACRO(PYTHON_CHECK_MODULE) + +ENDIF(NOT DEFINED INCLUDED_UHD_PYTHON_CMAKE) diff --git a/host/cmake/Modules/UHDVersion.cmake b/host/cmake/Modules/UHDVersion.cmake new file mode 100644 index 000000000..8f77188ee --- /dev/null +++ b/host/cmake/Modules/UHDVersion.cmake @@ -0,0 +1,65 @@ +# +# Copyright 2010-2012 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +INCLUDE(UHDPython) #requires python for parsing +FIND_PACKAGE(Git QUIET) + +######################################################################## +# Setup Version Numbers +# - increment major on api compatibility changes +# - increment minor on feature-level changes +# - increment patch on for bug fixes and docs +######################################################################## +SET(UHD_VERSION_MAJOR 003) +SET(UHD_VERSION_MINOR 004) +SET(UHD_VERSION_PATCH 000) + +######################################################################## +# Version information discovery through git log +######################################################################## +IF(UHD_RELEASE_MODE) + SET(UHD_BUILD_INFO_DISCOVERY FALSE) + SET(UHD_BUILD_INFO "release") +ELSE() + SET(UHD_BUILD_INFO_DISCOVERY GIT_FOUND) + SET(UHD_BUILD_INFO "unknown") +ENDIF() + +IF(UHD_BUILD_INFO_DISCOVERY) + + #grab the git ref id for the current head + EXECUTE_PROCESS( + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${GIT_EXECUTABLE} describe --always --abbrev=8 --long + OUTPUT_VARIABLE _git_describe OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _git_describe_result + ) + + #only set the build info on success + IF(_git_describe_result EQUAL 0) + EXECUTE_PROCESS( + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${PYTHON_EXECUTABLE} -c "print '${_git_describe}'.split('-',1)[1]" + OUTPUT_VARIABLE UHD_BUILD_INFO OUTPUT_STRIP_TRAILING_WHITESPACE + ) + ENDIF() + +ENDIF(UHD_BUILD_INFO_DISCOVERY) + +######################################################################## +SET(UHD_VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_MINOR}.${UHD_VERSION_PATCH}-${UHD_BUILD_INFO}") diff --git a/host/cmake/Toolchains/arm_cortex_a8_native.cmake b/host/cmake/Toolchains/arm_cortex_a8_native.cmake new file mode 100644 index 000000000..8e60eaab1 --- /dev/null +++ b/host/cmake/Toolchains/arm_cortex_a8_native.cmake @@ -0,0 +1,8 @@ +######################################################################## +# Toolchain file for building native on a ARM Cortex A8 w/ NEON +# Usage: cmake -DCMAKE_TOOLCHAIN_FILE=<this file> <source directory> +######################################################################## +set(CMAKE_CXX_COMPILER g++) +set(CMAKE_C_COMPILER gcc) +set(CMAKE_CXX_FLAGS "-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=softfp" CACHE STRING "" FORCE) +set(CMAKE_C_FLAGS ${CMAKE_CXX_FLAGS} CACHE STRING "" FORCE) #same flags for C sources diff --git a/host/cmake/cmake_uninstall.cmake.in b/host/cmake/cmake_uninstall.cmake.in new file mode 100644 index 000000000..6031a6ca9 --- /dev/null +++ b/host/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,23 @@ +# http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F + +IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +STRING(REGEX REPLACE "\n" ";" files "${files}") +FOREACH(file ${files}) + MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + IF(EXISTS "$ENV{DESTDIR}${file}") + EXEC_PROGRAM( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + IF(NOT "${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + ENDIF(NOT "${rm_retval}" STREQUAL 0) + ELSE(EXISTS "$ENV{DESTDIR}${file}") + MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + ENDIF(EXISTS "$ENV{DESTDIR}${file}") +ENDFOREACH(file) diff --git a/host/cmake/debian/postinst.in b/host/cmake/debian/postinst.in new file mode 100755 index 000000000..eaca15394 --- /dev/null +++ b/host/cmake/debian/postinst.in @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +if [ "$1" = "configure" ]; then + cp @CMAKE_INSTALL_PREFIX@/share/uhd/utils/uhd-usrp.rules /etc/udev/rules.d/uhd-usrp.rules + udevadm control --reload-rules + ldconfig +fi diff --git a/host/cmake/debian/postrm.in b/host/cmake/debian/postrm.in new file mode 100755 index 000000000..63a2e90a9 --- /dev/null +++ b/host/cmake/debian/postrm.in @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +if [ "$1" = "remove" ]; then + ldconfig +fi diff --git a/host/cmake/debian/preinst.in b/host/cmake/debian/preinst.in new file mode 100755 index 000000000..dc8795d8d --- /dev/null +++ b/host/cmake/debian/preinst.in @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +if [ "$1" = "install" ]; then + ls +fi diff --git a/host/cmake/debian/prerm.in b/host/cmake/debian/prerm.in new file mode 100755 index 000000000..30ce41963 --- /dev/null +++ b/host/cmake/debian/prerm.in @@ -0,0 +1,22 @@ +#!/bin/sh +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +if [ "$1" = "remove" ]; then + rm /etc/udev/rules.d/uhd-usrp.rules + udevadm control --reload-rules +fi diff --git a/host/cmake/msvc/inttypes.h b/host/cmake/msvc/inttypes.h new file mode 100644 index 000000000..1c2baa82e --- /dev/null +++ b/host/cmake/msvc/inttypes.h @@ -0,0 +1,301 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include <stdint.h> + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + + +#endif // _MSC_INTTYPES_H_ ] diff --git a/host/cmake/msvc/stdint.h b/host/cmake/msvc/stdint.h new file mode 100644 index 000000000..15333b467 --- /dev/null +++ b/host/cmake/msvc/stdint.h @@ -0,0 +1,226 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include <limits.h> + +// For Visual Studio 6 in C++ mode wrap <wchar.h> include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#if (_MSC_VER < 1300) && defined(__cplusplus) + extern "C++" { +#endif +# include <wchar.h> +#if (_MSC_VER < 1300) && defined(__cplusplus) + } +#endif + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef int intptr_t; + typedef unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h> +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +#ifndef INTMAX_C +#define INTMAX_C INT64_C +#endif +#ifndef UINTMAX_C +#define UINTMAX_C UINT64_C +#endif + +#endif // __STDC_CONSTANT_MACROS ] + + +#endif // _MSC_STDINT_H_ ] diff --git a/host/cmake/redhat/post_install.in b/host/cmake/redhat/post_install.in new file mode 100755 index 000000000..01fd3338f --- /dev/null +++ b/host/cmake/redhat/post_install.in @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +cp @CMAKE_INSTALL_PREFIX@/share/uhd/utils/uhd-usrp.rules /etc/udev/rules.d/uhd-usrp.rules +udevadm control --reload-rules +ldconfig diff --git a/host/cmake/redhat/post_uninstall.in b/host/cmake/redhat/post_uninstall.in new file mode 100755 index 000000000..5357680a9 --- /dev/null +++ b/host/cmake/redhat/post_uninstall.in @@ -0,0 +1,19 @@ +#!/bin/sh +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +ldconfig diff --git a/host/cmake/redhat/pre_install.in b/host/cmake/redhat/pre_install.in new file mode 100755 index 000000000..b611fb381 --- /dev/null +++ b/host/cmake/redhat/pre_install.in @@ -0,0 +1,17 @@ +#!/bin/sh +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# diff --git a/host/cmake/redhat/pre_uninstall.in b/host/cmake/redhat/pre_uninstall.in new file mode 100755 index 000000000..f1faa36ce --- /dev/null +++ b/host/cmake/redhat/pre_uninstall.in @@ -0,0 +1,20 @@ +#!/bin/sh +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +rm /etc/udev/rules.d/uhd-usrp.rules +udevadm control --reload-rules diff --git a/host/docs/CMakeLists.txt b/host/docs/CMakeLists.txt new file mode 100644 index 000000000..e393a79f0 --- /dev/null +++ b/host/docs/CMakeLists.txt @@ -0,0 +1,108 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# List of manual sources +######################################################################## +SET(manual_sources + index.rst + identification.rst + build.rst + calibration.rst + coding.rst + dboards.rst + gpsdo.rst + general.rst + images.rst + stream.rst + sync.rst + transport.rst + usrp1.rst + usrp2.rst + usrp_b1xx.rst + usrp_e1xx.rst +) + +######################################################################## +# Setup Manual +######################################################################## +MESSAGE(STATUS "") +FIND_PACKAGE(Docutils) + +LIBUHD_REGISTER_COMPONENT("Manual" ENABLE_MANUAL ON "DOCUTILS_FOUND" OFF) + +IF(ENABLE_MANUAL) + #setup rst2html options + SET(stylesheet ${CMAKE_CURRENT_SOURCE_DIR}/style.css) + SET(rst2html_options + --stylesheet=${stylesheet} + --no-toc-backlinks --date --time + ) + + #create generation rule for each source + FOREACH(rstfile ${manual_sources}) + #set input and output file names + SET(rstfile ${CMAKE_CURRENT_SOURCE_DIR}/${rstfile}) + GET_FILENAME_COMPONENT(rstfile_we ${rstfile} NAME_WE) + SET(htmlfile ${CMAKE_CURRENT_BINARY_DIR}/${rstfile_we}.html) + + #make the html file depend on the rst file + ADD_CUSTOM_COMMAND( + OUTPUT ${htmlfile} DEPENDS ${rstfile} ${stylesheet} + COMMAND ${RST2HTML_EXECUTABLE} ${rstfile} ${htmlfile} ${rst2html_options} + COMMENT "Generating ${htmlfile}" + ) + + #make the manual target depend on the html file + LIST(APPEND manual_html_files ${htmlfile}) + INSTALL(FILES ${htmlfile} DESTINATION ${PKG_DOC_DIR}/manual/html COMPONENT manual) + ENDFOREACH(rstfile ${manual_sources}) + + #make the html manual a build-time dependency + ADD_CUSTOM_TARGET(manual_html ALL DEPENDS ${manual_html_files}) +ENDIF(ENABLE_MANUAL) + +INSTALL(FILES ${manual_sources} DESTINATION ${PKG_DOC_DIR}/manual/rst COMPONENT manual) + +######################################################################## +# Setup Doxygen +######################################################################## +MESSAGE(STATUS "") +FIND_PACKAGE(Doxygen) + +LIBUHD_REGISTER_COMPONENT("Doxygen" ENABLE_DOXYGEN ON "DOXYGEN_FOUND" OFF) + +IF(ENABLE_DOXYGEN) + #generate the doxygen configuration file + SET(CMAKE_CURRENT_BINARY_DIR_DOXYGEN ${CMAKE_CURRENT_BINARY_DIR}/doxygen) + CONFIGURE_FILE( + ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in + ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + @ONLY) + + #make doxygen directory depend on the header files + FILE(GLOB_RECURSE header_files ${CMAKE_SOURCE_DIR}/include/*.hpp) + ADD_CUSTOM_COMMAND( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR_DOXYGEN} DEPENDS ${header_files} + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + COMMENT "Generating documentation with doxygen" + ) + + #make the doxygen generation a built-time dependency + ADD_CUSTOM_TARGET(doxygen_docs ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR_DOXYGEN}) + INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR_DOXYGEN} DESTINATION ${PKG_DOC_DIR} COMPONENT doxygen) +ENDIF(ENABLE_DOXYGEN) diff --git a/host/docs/Doxyfile.in b/host/docs/Doxyfile.in new file mode 100644 index 000000000..7395516b5 --- /dev/null +++ b/host/docs/Doxyfile.in @@ -0,0 +1,1514 @@ +# Doxyfile 1.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = @CPACK_PACKAGE_NAME@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @CPACK_PACKAGE_VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR_DOXYGEN@ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @CMAKE_SOURCE_DIR@/include + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.hpp + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) +# there is already a search function so this one should typically +# be disabled. + +SEARCHENGINE = YES + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/host/docs/build.rst b/host/docs/build.rst new file mode 100644 index 000000000..fbd582880 --- /dev/null +++ b/host/docs/build.rst @@ -0,0 +1,216 @@ +======================================================================== +UHD - Build Guide +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Build Dependencies +------------------------------------------------------------------------ + +**Linux Notes:** +This is dependent on the distribution you are using, but most, if not all, of +the dependencies should be available in the package repositories for your +package manager. + +**Mac OS X Notes:** +Install the "Xcode Developer Tools" to get the build tools (gcc and make). +Use MacPorts to get the Boost and Cheetah dependencies. +Other dependencies can be downloaded as dmg installers from the web. + +**Windows Notes:** +The dependencies can be acquired through installable exe files. +Usually, the windows installer can be found on the project's website. +Some projects do not host windows installers, and if this is the case, +follow the auxiliary download url for the windows installer (below). + +^^^^^^^^^^^^^^^^ +Git +^^^^^^^^^^^^^^^^ +Required to check out the repository. +On windows, install cygwin with git support to checkout the repository, +or install msysgit from http://code.google.com/p/msysgit/downloads/list + +^^^^^^^^^^^^^^^^ +C++ compiler +^^^^^^^^^^^^^^^^ +The following compilers are known to work: + +* GCC +* Clang +* MSVC + +^^^^^^^^^^^^^^^^ +CMake +^^^^^^^^^^^^^^^^ +* **Purpose:** generates project build files +* **Version:** at least 2.6 +* **Usage:** build time (required) +* **Download URL:** http://www.cmake.org/cmake/resources/software.html + +^^^^^^^^^^^^^^^^ +Boost +^^^^^^^^^^^^^^^^ +* **Purpose:** C++ library +* **Version:** at least 1.36 unix, at least 1.40 windows +* **Usage:** build time + run time (required) +* **Download URL:** http://www.boost.org/users/download/ +* **Download URL (windows installer):** http://www.boostpro.com/download + +^^^^^^^^^^^^^^^^ +LibUSB +^^^^^^^^^^^^^^^^ +* **Purpose:** USB-based hardware support +* **Version:** at least 1.0 +* **Usage:** build time + run time (optional) +* **Download URL:** http://sourceforge.net/projects/libusb/files/libusb-1.0/ +* **Download URL (windows binaries):** http://www.libusb.org/wiki/windows_backend#LatestBinarySnapshots + +^^^^^^^^^^^^^^^^ +Python +^^^^^^^^^^^^^^^^ +* **Purpose:** used by Cheetah and utility scripts +* **Version:** at least 2.6 +* **Usage:** build time + run time utility scripts (required) +* **Download URL:** http://www.python.org/download/ + +^^^^^^^^^^^^^^^^ +Cheetah +^^^^^^^^^^^^^^^^ +* **Purpose:** source code generation +* **Version:** at least 2.0 +* **Usage:** build time (required) +* **Download URL:** http://www.cheetahtemplate.org/download.html +* **Download URL (windows installer):** http://feisley.com/python/cheetah/ + +**Alternative method:** +Install setuptools, and use the easy_install command to install Cheetah. +http://pypi.python.org/pypi/setuptools + +^^^^^^^^^^^^^^^^ +Doxygen +^^^^^^^^^^^^^^^^ +* **Purpose:** generates html api documentation +* **Usage:** build time (optional) +* **Download URL:** http://www.stack.nl/~dimitri/doxygen/download.html#latestsrc + +^^^^^^^^^^^^^^^^ +Docutils +^^^^^^^^^^^^^^^^ +* **Purpose:** generates html user manual +* **Usage:** build time (optional) +* **Download URL:** http://docutils.sourceforge.net/ + +------------------------------------------------------------------------ +Build Instructions (Unix) +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Generate Makefiles with cmake +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + cd <uhd-repo-path>/host + mkdir build + cd build + cmake ../ + +Additionally, configuration variables can be passed into cmake via the command line. +The following common-use configuration variables are listed below: + +* For a custom install prefix: -DCMAKE_INSTALL_PREFIX=<install-path> +* To install libs into lib64: cmake -DLIB_SUFFIX=64 + +Example usage: +:: + + cmake -DCMAKE_INSTALL_PREFIX=/opt/uhd ../ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Build and install +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + make + make test + sudo make install + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Setup the library path (Linux) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Make sure that libuhd.so is in your LD_LIBRARY_PATH +or add it to /etc/ld.so.conf and make sure to run: +:: + + sudo ldconfig + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Setup the library path (Mac OS X) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Make sure that libuhd.dylib is in your DYLD_LIBRARY_PATH + +------------------------------------------------------------------------ +Build Instructions (Windows) +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Generate the project with cmake +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* Open the cmake gui program. +* Set the path to the source code: <uhd-repo-path>/host +* Set the path to the build directory: <uhd-repo-path>/host/build +* Make sure that the paths do not contain spaces. +* Click configure and select the MSVC compiler. +* Set the build variables and click configure again. +* Click generate and a project file will be created in the build directory. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LibUSB cmake notes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +On Windows, cmake does not have the advantage of pkg-config, +so we must manually tell cmake how to locate the LibUSB header and lib. + +* From the cmake gui, select "Advanded View" +* Set LIBUSB_INCLUDE_DIRS to the directory with "libusb.h". +* Set LIBUSB_LIBRARIES to the full path for "libusb-1.0.lib". + + * Recommend the static libusb-1.0.lib to simplify runtime dependencies. + +* Check the box to enable USB support, click configure and generate. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Build the project in MSVC +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* Open the generated project file in MSVC. +* Change the build type from "Debug" to "Release". +* Select the build all target, right click, and choose build. +* Select the install target, right click, and choose build. + +**Note:** you may not have permission to build the install target. +You need to be an administrator or to run MSVC as administrator. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Build the project in MSVC (command line) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Open the Visual Studio Command Prompt Shorcut: +:: + + cd <uhd-repo-path>\host\build + DevEnv uhd.sln /build Release /project ALL_BUILD + DevEnv uhd.sln /build Release /project INSTALL + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Setup the PATH environment variable +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* Add the uhd bin path to %PATH% (usually c:\\program files\\uhd\\bin) + +**Note:** +The interface for editing environment variable paths in Windows is very poor. +I recommend using "Rapid Environment Editor" (http://www.rapidee.com) over the default editor. + +------------------------------------------------------------------------ +Post-Install Tasks +------------------------------------------------------------------------ +For USB-based devices, +see the `USB Transport Application Notes <./transport.html#usb-transport-libusb>`_ +for platform-specific post-installation tasks. diff --git a/host/docs/calibration.rst b/host/docs/calibration.rst new file mode 100644 index 000000000..14e66a312 --- /dev/null +++ b/host/docs/calibration.rst @@ -0,0 +1,61 @@ +======================================================================== +UHD - Calibration Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Self-calibration +------------------------------------------------------------------------ +The UHD comes with several self-calibration utilities for minimizing IQ imbalance and DC offset. +These utilities perform calibration sweeps using transmit leakage into the receive path +(special equipment is not required). +The results from a calibration are written to a csv file in the user's home directory. +UHD will automatically apply corrections at runtime when the user re-tunes the daughterboard LO. +Calibration results are specific to an individual RF board. + +**Note:** +When a calibration table is present, +and the user wishes to override the calibration settings through the API: +the user should re-apply the desired setting every time the LO is re-tuned. + +UHD comes with the following calibration utilities: + + * **uhd_cal_rx_iq_balance:** - mimimizes RX IQ imbalance vs LO frequency + * **uhd_cal_tx_dc_offset:** - mimimizes TX DC offset vs LO frequency + * **uhd_cal_tx_iq_balance:** - mimimizes TX IQ imbalance vs LO frequency + + +The following RF frontends are supported by the self-calibration utilities: + + * WBX transceiver board + * SBX transceiver board + * more to come... + +******************************************** +Calibration utilities +******************************************** +UHD installs the calibration utilities into <install-path>/bin. +**Disconnect** any extrernal hardware from the RF antenna ports, +and run the following from the command line. +Each utility will take several minutes to complete. +:: + + uhd_cal_rx_iq_balance --verbose --args=<optional device args> + uhd_cal_tx_iq_balance --verbose --args=<optional device args> + uhd_cal_tx_dc_offset --verbose --args=<optional device args> + +See the output given by --help for more advanced options, such as: +manually choosing the frequency range and step size for the sweeps. + +******************************************** +Calibration data +******************************************** +Calibration files are stored in the user's home/application directory. +They can easily be moved from machine to another by copying the "cal" directory. +Re-running a calibration utility will replace the existing calibration file. +The old calibration file will be renamed so it may be recovered by the user. + + * **Unix:** ${HOME}/.uhd/cal/ + * **Windows:** %APPDATA%\\.uhd\\cal\\ + diff --git a/host/docs/coding.rst b/host/docs/coding.rst new file mode 100644 index 000000000..ed858ceb4 --- /dev/null +++ b/host/docs/coding.rst @@ -0,0 +1,30 @@ +======================================================================== +UHD - Coding to the API +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Various API interfaces +------------------------------------------------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Low-Level: The device API +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +A device is an abstraction for hardware that is connected to the host system. +For a USRP, this means that the motherboard and everything on it would be +considered to be a "device". The device API provides ways to: + +* Discover devices that are physically connected to the host system. +* Create a device object for a particular device identified by address. +* Register a device driver into the discovery and factory sub-system. +* Streaming samples with metadata into and out of the device. +* Set and get properties on the device object. + +See the documentation in *device.hpp* for reference. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +High-Level: The multi usrp +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The Multi-USRP class provides a fat interface to a single USRP with +one or more channels, or multiple USRPs in a homogeneous setup. +See the documentation in *usrp/multi_usrp.hpp* for reference. diff --git a/host/docs/dboards.rst b/host/docs/dboards.rst new file mode 100644 index 000000000..d41bf824c --- /dev/null +++ b/host/docs/dboards.rst @@ -0,0 +1,381 @@ +======================================================================== +UHD - Daughterboard Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Daughterboard Properties +------------------------------------------------------------------------ + +The following contains interesting notes about each daughterboard. +Eventually, this page will be expanded to list out the full +properties of each board as well. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Basic RX and and LFRX +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The Basic RX and LFRX boards have 4 frontends: + +* **Frontend A:** real signal on antenna RXA +* **Frontend B:** real signal on antenna RXB +* **Frontend AB:** quadrature frontend using both antennas (IQ) +* **Frontend BA:** quadrature frontend using both antennas (QI) + +The boards have no tunable elements or programmable gains. +Though the magic of aliasing, you can down-convert signals +greater than the Nyquist rate of the ADC. + +BasicRX Bandwidth (Hz): + +* For Real-Mode (A or B frontend): 250M +* For Complex (AB or BA frontend): 500M + +LFRX Bandwidth (Hz): + +* For Real-Mode (A or B frontend): 33M +* For Complex (AB or BA frontend): 66M + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Basic TX and and LFTX +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The Basic TX and LFTX boards have 4 frontends: + +* **Frontend A:** real signal on antenna TXA +* **Frontend B:** real signal on antenna TXB +* **Frontend AB:** quadrature frontend using both antennas (IQ) +* **Frontend BA:** quadrature frontend using both antennas (QI) + +The boards have no tunable elements or programmable gains. +Though the magic of aliasing, you can up-convert signals +greater than the Nyquist rate of the DAC. + +BasicTX Bandwidth (Hz): 250M + +* For Real-Mode (A or B frontend): 250M +* For Complex (AB or BA frontend): 500M + +LFTX Bandwidth (Hz): 33M + +* For Real-Mode (A or B frontend): 33M +* For Complex (AB or BA frontend): 66M + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +DBSRX +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The DBSRX board has 1 quadrature frontend. +It defaults to direct conversion, but can use a low IF through lo_offset in uhd::tune_request_t + +Receive Antennas: **J3** + +* **Frontend 0:** Complex baseband signal from antenna J3 + +The board has no user selectable antenna setting + +Receive Gains: + +* **GC1**, Range: 0-56dB +* **GC2**, Range: 0-24dB + +Bandwidth (Hz): 8M-66M + +Sensors: + +* **lo_locked**: boolean for LO lock state + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +DBSRX2 +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The DBSRX2 board has 1 quadrature frontend. +It defaults to direct conversion, but can use a low IF through lo_offset in uhd::tune_request_t + +Receive Antennas: **J3** + +* **Frontend 0:** Complex baseband signal from antenna J3 + +The board has no user selectable antenna setting + +Receive Gains: + +* **GC1**, Range: 0-73dB +* **BBG**, Range: 0-15dB + +Bandwidth (Hz): 8M-80M + +Sensors: + +* **lo_locked**: boolean for LO lock state + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +RFX Series +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The RFX Series boards have 2 quadrature frontends, one transmit, one receive. +Transmit defaults to low IF and Receive defaults to direct conversion. +The IF can be adjusted through lo_offset in uhd::tune_request_t + +The RFX Series boards have independent receive and transmit LO's and synthesizers +allowing full-duplex operation on different transmit and receive frequencies. + +Transmit Antennas: **TX/RX** + +Receive Antennas: **TX/RX** or **RX2** + +* **Frontend 0:** Complex baseband signal for selected antenna + +The user may set the receive antenna to be TX/RX or RX2. +However, when using an RFX board in full-duplex mode, +the receive antenna will always be set to RX2, regardless of the settings. + +Receive Gains: **PGA0**, Range: 0-70dB (except RFX400 range is 0-45dB) + +Bandwidths (Hz): + +* **RX**: 40M +* **TX**: 40M + +Sensors: + +* **lo_locked**: boolean for LO lock state + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +XCVR 2450 +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The XCVR2450 has 2 quadrature frontends, one transmit, one receive. +Transmit and Receive default to direct conversion but +can be used in low IF mode through lo_offset in uhd::tune_request_t + +The XCVR2450 has a non-contiguous tuning range consisting of a +high band (4.9-6.0GHz) and a low band (2.4-2.5GHz). + +Transmit Antennas: **J1** or **J2** + +Receive Antennas: **J1** or **J2** + +* **Frontend 0:** Complex baseband signal for selected antenna + +The XCVR2450 uses a common LO for both receive and transmit. +Even though the API allows the RX and TX LOs to be individually set, +a change of one LO setting will be reflected in the other LO setting. + +The XCVR2450 does not support full-duplex mode, attempting to operate +in full-duplex will result in transmit-only operation. + +Transmit Gains: + +* **VGA**, Range: 0-30dB +* **BB**, Range: 0-5dB + +Receive Gains: + +* **LNA**, Range: 0-30.5dB +* **VGA**, Range: 0-62dB + +Bandwidths (Hz): + +* **RX**: 15M, 19M, 28M, 36M; (each +-0, 5, or 10%) +* **TX**: 24M, 36M, 48M + +Sensors: + +* **lo_locked**: boolean for LO lock state +* **rssi**: float for rssi in dBm + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +WBX Series +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The WBX Series boards have 2 quadrature frontends, one transmit, one receive. +Transmit and Receive default to direct conversion but +can be used in low IF mode through lo_offset in uhd::tune_request_t + +The WBX Series boards have independent receive and transmit LO's and synthesizers +allowing full-duplex operation on different transmit and receive frequencies. + +Transmit Antennas: **TX/RX** + +Receive Antennas: **TX/RX** or **RX2** + +* **Frontend 0:** Complex baseband signal for selected antenna + +The user may set the receive antenna to be TX/RX or RX2. +However, when using an WBX board in full-duplex mode, +the receive antenna will always be set to RX2, regardless of the settings. + +Transmit Gains: **PGA0**, Range: 0-25dB + +Receive Gains: **PGA0**, Range: 0-31.5dB + +Bandwidths (Hz): + +* **RX**: 40M +* **TX**: 40M + +Sensors: + +* **lo_locked**: boolean for LO lock state + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +SBX Series +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The SBX Series boards have 2 quadrature frontends, one transmit, one receive. +Transmit and Receive default to direct conversion but +can be used in low IF mode through lo_offset in uhd::tune_request_t + +The SBX Series boards have independent receive and transmit LO's and synthesizers +allowing full-duplex operation on different transmit and receive frequencies. + +Transmit Antennas: **TX/RX** + +Receive Antennas: **TX/RX** or **RX2** + +* **Frontend 0:** Complex baseband signal for selected antenna + +The user may set the receive antenna to be TX/RX or RX2. +However, when using an SBX board in full-duplex mode, +the receive antenna will always be set to RX2, regardless of the settings. + +Transmit Gains: **PGA0**, Range: 0-31.5dB + +Receive Gains: **PGA0**, Range: 0-31.5dB + +Bandwidths (Hz): + +* **RX**: 40M +* **TX**: 40M + +Sensors: + +* **lo_locked**: boolean for LO lock state + +LEDs: + +* All LEDs flash when dboard control is initialized +* **TX LD**: Transmit Synthesizer Lock Detect +* **TX/RX**: Receiver on TX/RX antenna port (No TX) +* **RX LD**: Receive Synthesizer Lock Detect +* **RX1/RX2**: Receiver on RX2 antenna port + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TVRX +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The TVRX board has 1 real-mode frontend. +It is operated at a low IF. + +Receive Antennas: RX + +* **Frontend 0:** real-mode baseband signal from antenna RX + +Receive Gains: + +* **RF**, Range: -13.3-50.3dB (frequency-dependent) +* **IF**, Range: -1.5-32.5dB + +Bandwidth: 6MHz + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TVRX2 +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The TVRX2 board has 2 real-mode frontends. +It is operated at a low IF. + +Receive Frontends: + +* **Frontend RX1:** real-mode baseband from antenna J100 +* **Frontend RX2:** real-mode baseband from antenna J140 + +Note: The TVRX2 has always-on AGC, the software controllable gain is the +final gain stage which controls the AGC set-point for output to ADC. + +Receive Gains: + +* **IF**, Range: 0.0-30.0dB + +Bandwidth: 1.7MHz, 6MHz, 7MHz, 8MHz, 10MHz + +Sensors: + +* **lo_locked**: boolean for LO lock state +* **rssi**: float for measured RSSI in dBm +* **temperature**: float for measured temperature in degC + +------------------------------------------------------------------------ +Daughterboard Modifications +------------------------------------------------------------------------ + +Sometimes, daughterboards will require modification +to work on certain frequencies or to work with certain hardware. +Modification usually involves moving/removing a SMT component +and burning a new daughterboard id into the eeprom. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +DBSRX - Mod +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Due to different clocking capabilities, +the DBSRX will require modifications to operate on a non-USRP1 motherboard. +On a USRP1 motherboard, a divided clock is provided from an FPGA pin +because the standard daughterboard clock lines cannot provided a divided clock. +However, on other USRP motherboards, the divided clock is provided +over the standard daughterboard clock lines. + +**Step 1: Move the clock configuration resistor** + +Remove R193 (which is 10 ohms, 0603 size) and put it on R194, which is empty. +This is made somewhat more complicated by the fact that the silkscreen is not clear in that area. +R193 is on the back, immediately below the large beige connector, J2. +R194 is just below, and to the left of R193. +The silkscreen for R193 is ok, but for R194, +it is upside down, and partially cut off. +If you lose R193, you can use anything from 0 to 10 ohms there. + +**Step 2: Burn a new daughterboard id into the EEPROM** + +With the daughterboard plugged-in, run the following commands: +:: + + cd <install-path>/share/uhd/utils + ./usrp_burn_db_eeprom --id=0x000d --unit=RX --args=<args> --slot=<slot> + +* <args> are device address arguments (optional if only one USRP is on your machine) +* <slot> is the name of the daughterboard slot (optional if the USRP has only one slot) + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +RFX - Mod +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Older RFX boards require modifications to use the motherboard oscillator. +If this is the case, UHD will print a warning about the modification. +Please follow the modification procedures below: + +**Step 1: Disable the daughterboard clocks** + +Move R64 to R84, Move R142 to R153 + +**Step 2: Connect the motherboard blocks** + +Move R35 to R36, Move R117 to R115 +These are all 0-ohm, so if you lose one, just short across the appropriate pads + +**Step 3: Burn the appropriate daughterboard id into the EEPROM** + +With the daughterboard plugged-in, run the following commands: +:: + + cd <install-path>/share/uhd/utils + ./usrp_burn_db_eeprom --id=<rx_id> --unit=RX --args=<args> --slot=<slot> + ./usrp_burn_db_eeprom --id=<tx_id> --unit=TX --args=<args> --slot=<slot> + +* <rx_id> choose the appropriate RX ID for your daughterboard + + * **RFX400:** 0x0024 + * **RFX900:** 0x0025 + * **RFX1800:** 0x0034 + * **RFX1200:** 0x0026 + * **RFX2400:** 0x0027 +* <tx_id> choose the appropriate TX ID for your daughterboard + + * **RFX400:** 0x0028 + * **RFX900:** 0x0029 + * **RFX1800:** 0x0035 + * **RFX1200:** 0x002a + * **RFX2400:** 0x002b +* <args> are device address arguments (optional if only one USRP is on your machine) +* <slot> is the name of the daughterboard slot (optional if the USRP has only one slot) diff --git a/host/docs/general.rst b/host/docs/general.rst new file mode 100644 index 000000000..5df89fc19 --- /dev/null +++ b/host/docs/general.rst @@ -0,0 +1,224 @@ +======================================================================== +UHD - General Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Tuning notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Two-stage tuning process +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +A USRP device has two stages of tuning: + +* RF front-end: translates bewteen RF and IF +* DSP: translates between IF and baseband + +In a typical use-case, the user specifies an overall center frequency for the +signal chain. The RF front-end will be tuned as close as possible to the center +frequency, and the DSP will account for the error in tuning between target +frequency and actual frequency. The user may also explicitly control both +stages of tuning through through the tune_request_t object, which allows for +more advanced tuning. + +In general, Using UHD's advanced tuning is highly recommended as it makes it +easy to move the DC component out of your band-of-interest. This can be done by +passing your desired LO offset to the tune_request_t object, and letting UHD +handle the rest. + +Tuning the receive chain: +:: + + //tuning to a desired center frequency + usrp->set_rx_freq(target_frequency_in_hz); + + --OR-- + + //advanced tuning with tune_request_t + uhd::tune_request_t tune_req(target_frequency_in_hz, desired_lo_offset); + //fill in any additional/optional tune request fields... + usrp->set_rx_freq(tune_req); + +More information can be fonud in *tune_request.hpp*. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +RF front-end settling time +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +After tuning, the RF front-end will need time to settle into a usable state. +Typically, this means that the local oscillators must be given time to lock +before streaming begins. Lock time is not consistent; it varies depending upon +the device and requested settings. After tuning and before streaming, the user +should wait for the "lo_locked" sensor to become true, or sleep for +a conservative amount of time (perhaps a second). + +Pseudo-code for dealing with settling time after tuning on receive: +:: + + usrp->set_rx_freq(...); + sleep(1); + usrp->issue_stream_command(...); + + --OR-- + + usrp->set_rx_freq(...); + while (not usrp->get_rx_sensor("lo_locked").to_bool()){ + //sleep for a short time in milliseconds + } + usrp->issue_stream_command(...); + +------------------------------------------------------------------------ +Specifying the subdevice to use +------------------------------------------------------------------------ +A subdevice specification string for USRP family devices is composed of: + +:: + + <motherboard slot name>:<daughterboard frontend name> + +Ex: The subdev spec markup string to select a WBX on slot B. + +:: + + B:0 + +Ex: The subdev spec markup string to select a BasicRX on slot B. + +:: + + B:AB + + -- OR -- + + B:A + + -- OR -- + + B:B + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +USRP Family Motherboard Slot Names +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +All USRP family motherboards have a first slot named **A:**. The USRP1 has +two daughterboard subdevice slots, known as **A:** and **B:**. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Daughterboard Frontend Names +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Daughterboard frontend names can be used to specify which signal path is used +from a daughterboard. Most daughterboards have only one frontend **:0**. A few +daughterboards (Basic, LF and TVRX2) have multiple frontend names available. +The frontend names are documented in the +`Daughterboard Application Notes <./dboards.html>`_ + +------------------------------------------------------------------------ +Overflow/Underflow notes +------------------------------------------------------------------------ +**Note:** The following overflow/underflow notes do not apply to USRP1, +which does not support the advanced features available in newer products. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Overflow notes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When receiving, the device produces samples at a constant rate. +Overflows occurs when the host does not consume data fast enough. +When UHD detects the overflow, it prints an "O" to stdout, +and pushes an inline message packet into the receive stream. + +**Network-based devices**: +The host does not back-pressure the receive stream. +When the kernel's socket buffer becomes full, it will drop subsequent packets. +UHD detects the overflow as a discontinuity in the packet's sequence numbers, +and muxes an inline message packet into the receive stream. + +**Other devices**: +The host back-pressures the receive stream. +Therefore, overflows always occur in the device itself. +When the device's internal buffers become full, streaming is shutoff, +and an inline message packet is sent to the host. +If the device was in continuous streaming mode, +the UHD will automatically restart streaming. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Underflow notes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When transmitting, the device consumes samples at a constant rate. +Underflow occurs when the host does not produce data fast enough. +When the UHD detects underflow, it prints an "U" to stdout, +and pushes a message packet into the async message stream. + +------------------------------------------------------------------------ +Threading notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Thread safety notes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +For the most part, UHD is thread-safe. +Please observe the following limitations: + +**Fast-path thread requirements:** +There are three fast-path methods for a device: send(), recv(), and recv_async_msg(). +All three methods are thread-safe and can be called from different thread contexts. +For performance, the user should call each method from a separate thread context. +These methods can also be used in a non-blocking fashion by using a timeout of zero. + +**Slow-path thread requirements:** +It is safe to change multiple settings simultaneously. However, +this could leave the settings for a device in an uncertain state. +The is because changing one setting could have an impact on how a call affects other settings. +Example: setting the channel mapping affects how the antennas are set. +It is recommended to use at most one thread context for manipulating device settings. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Thread priority scheduling +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When the UHD spawns a new thread it may try to boost the thread's scheduling priority. +When setting the priority fails, the UHD prints out an error. +This error is harmless, it simply means that the thread will have a normal scheduling priority. + +**Linux Notes:** + +Non-privileged users need special permission to change the scheduling priority. +Add the following line to */etc/security/limits.conf*: +:: + + @<my_group> - rtprio 99 + +Replace <my_group> with a group to which your user belongs. +Settings will not take effect until the user has logged in and out. + +------------------------------------------------------------------------ +Misc notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Support for dynamically loadable modules +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +For a module to be loaded at runtime, it must be: + +* found in the UHD_MODULE_PATH environment variable, +* installed into the <install-path>/share/uhd/modules directory, +* or installed into /usr/share/uhd/modules directory (unix only). + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Disabling or redirecting prints to stdout +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The user can disable the UHD library from printing directly to stdout by registering a custom message handler. +The handler will intercept all messages, which can be dropped or redirected. +Only one handler can be registered at a time. +Make "register_handler" your first call into UHD: + +:: + + #include <uhd/utils/msg.hpp> + + void my_handler(uhd::msg::type_t type, const std::string &msg){ + //handle the message... + } + + uhd::msg::register_handler(&my_handler); diff --git a/host/docs/gpsdo.rst b/host/docs/gpsdo.rst new file mode 100644 index 000000000..e84af8500 --- /dev/null +++ b/host/docs/gpsdo.rst @@ -0,0 +1,80 @@ +======================================================================== +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 + +**Antenna Types:** + +The GPSDO is capable of supplying a 3V for active GPS antennas or supporting passive antennas + +------------------------------------------------------------------------ +Installation instructions +------------------------------------------------------------------------ +Installation instructions can be found here: +`www.ettus.com/downloads/gpsdo-kit.pdf <http://www.ettus.com/downloads/gpsdo-kit.pdf>`_ + +******************************************** +Post installation task (N-Series only) +******************************************** +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 Notes <./sync.html>`_), +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 --args=<optional device args> --key=gpsdo --val=internal + + -- restore original setting -- + ./usrp_burn_mb_eeprom --args=<optional device args> --key=gpsdo --val=none + +------------------------------------------------------------------------ +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/identification.rst b/host/docs/identification.rst new file mode 100644 index 000000000..deda61531 --- /dev/null +++ b/host/docs/identification.rst @@ -0,0 +1,125 @@ +======================================================================== +UHD - Device Identification Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Identifying USRPs +------------------------------------------------------------------------ +Devices are addressed through key/value string pairs. +These string pairs can be used to narrow down the search for a specific device or group of devices. +Most UHD utility applications and examples have a --args parameter that takes a device address; +where the device address is expressed as a delimited string. +See the documentation in types/device_addr.hpp for reference. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Common device identifiers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Every device has several ways of identifying it on the host system: + ++------------+------------+--------------------------------------------+ +| Identifier | Key | Notes | ++============+============+============================================+ +| Serial | serial | globally unique identifier | ++------------+------------+--------------------------------------------+ +| Address | addr | unique identifier on a network | ++------------+------------+--------------------------------------------+ +| Name | name | optional user-set identifier | ++------------+------------+--------------------------------------------+ +| Type | type | hardware series identifier | ++------------+------------+--------------------------------------------+ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Device discovery via command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Devices attached to your system can be discovered using the "uhd_find_devices" program. +The find devices program scans your system for supported devices and prints +out an enumerated list of discovered devices and their addresses. +The list of discovered devices can be narrowed down by specifying device address args. + +:: + + uhd_find_devices + +Device address arguments can be supplied to narrow the scope of the search. + +:: + + uhd_find_devices --args="type=usrp1" + + -- OR -- + + uhd_find_devices --args="serial=12345678" + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Device discovery through the API +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The device::find() API call searches for devices and returns a list of discovered devices. + +:: + + uhd::device_addr_t hint; //an empty hint discovers all devices + uhd::device_addrs_t dev_addrs = uhd::device::find(hint); + +The hint argument can be populated to narrow the scope of the search. + +:: + + uhd::device_addr_t hint; + hint["type"] = "usrp1"; + uhd::device_addrs_t dev_addrs = uhd::device::find(hint); + + -- OR -- + + uhd::device_addr_t hint; + hint["serial"] = "12345678"; + uhd::device_addrs_t dev_addrs = uhd::device::find(hint); + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Device properties +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Properties of devices attached to your system can be probed with the "uhd_usrp_probe" program. +The usrp probe program constructs an instance of the device and prints out its properties; +properties such as detected daughter-boards, frequency range, gain ranges, etc... + +**Usage:** +:: + + uhd_usrp_probe --args <device-specific-address-args> + +------------------------------------------------------------------------ +Naming a USRP +------------------------------------------------------------------------ +For convenience purposes, users may assign a custom name to their USRPs. +The USRP can then be identified via name, rather than a difficult to remember serial or address. + +A name has the following properties: + +* is composed of ASCII characters +* is between 0 and 20 characters +* is not required to be unique + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Set a custom name +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Run the following commands: +:: + + cd <install-path>/share/uhd/utils + ./usrp_burn_mb_eeprom --args=<optional device args> --key=name --val=lab1_xcvr + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Discovery via name +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The keyword "name" can be used to narrow the scope of the search. +Example with the find devices utility: +:: + + uhd_find_devices --args="name=lab1_xcvr" + + -- OR -- + + uhd_find_devices --args="type=usrp1, name=lab1_xcvr" diff --git a/host/docs/images.rst b/host/docs/images.rst new file mode 100644 index 000000000..a25268990 --- /dev/null +++ b/host/docs/images.rst @@ -0,0 +1,93 @@ +======================================================================== +UHD - Firmware and FPGA Image Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Images Overview +------------------------------------------------------------------------ +Every USRP device must be loaded with special firmware and FPGA images. +The methods of loading images into the device varies among devices: + +* **USRP1:** The host code will automatically load the firmware and FPGA at runtime. +* **USRP2:** The user must manually write the images onto the USRP2 SD card. +* **USRP-N Series:** The user must manually transfer the images over ethernet. +* **USRP-E Series:** The host code will automatically load the FPGA at runtime. +* **USRP-B Series:** The host code will automatically load the FPGA at runtime. + +------------------------------------------------------------------------ +Pre-built images +------------------------------------------------------------------------ + +Pre-built images are available for download. +See the UHD wiki for the download link. + +The pre-built images come in two forms: + +* bundled with UHD in a platform-specific installer +* stand-alone platform-independent archive files + +^^^^^^^^^^^^^^^^^^^^^^ +Platform installers +^^^^^^^^^^^^^^^^^^^^^^ +The UNIX-based installers will install the images into /usr/share/uhd/images. +On windows, the images will be installed to <install-path>/share/uhd/images. + +^^^^^^^^^^^^^^^^^^^^^^ +Archive install +^^^^^^^^^^^^^^^^^^^^^^ +When installing images from an archive, there are two options: + +**Option 1:** + +Unpack the archive into the UHD installation prefix. +The UHD will always search <install-path>/share/uhd/images for image files. +Where <install-path> was set by the CMAKE_INSTALL_PREFIX at configure-time. + +**Option 2:** + +Unpack the archive anywhere and set the UHD_IMAGE_PATH environment variable. +The UHD_IMAGE_PATH may contain a list of directories to search for image files. + +------------------------------------------------------------------------ +Building images +------------------------------------------------------------------------ + +The UHD source repository comes with the source code necessary to build +both firmware and FPGA images for all supported devices. +The build commands for a particular image can be found in <uhd-repo-path>/images/Makefile. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Xilinx FPGA builds +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Xilinx ISE 12.x and up is required to build the Xilinx FPGA images. +The build requires that you have a unix-like environment with make. +Make sure that xtclsh from the Xilinx ISE bin directory is in your $PATH. + +See <uhd-repo-path>/fpga/usrp2/top/* + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ZPU firmware builds +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The ZPU GCC compiler is required to build the ZPU firmware images. +The build requires that you have a unix-like environment with cmake and make. +Make sure that zpu-elf-gcc is in your $PATH. + +See <uhd-repo-path>/firmware/zpu + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Altera FPGA builds +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Quartus is required to build the Altera FPGA images. +Pre-built images can also be found in <uhd-repo-path>/fpga/usrp1/rbf + +See <uhd-repo-path>/fpga/usrp1/toplevel/* + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +FX2 firmware builds +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The sdcc compiler is required to build the FX2 firmware images. +The build requires that you have a unix-like environment with cmake and make. + +See <uhd-repo-path>/firmware/fx2 diff --git a/host/docs/index.rst b/host/docs/index.rst new file mode 100644 index 000000000..f881e8585 --- /dev/null +++ b/host/docs/index.rst @@ -0,0 +1,41 @@ +======================================================================== +UHD - USRP Hardware Driver +======================================================================== + +The UHD is the "Universal Software Radio Peripheral" hardware driver. +The goal of the UHD is to provide a host driver and API for current and future Ettus Research products. +Users will be able to use the UHD driver standalone or with 3rd party applications. + +------------------------------------------------------------------------ +Contents +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^ +Building the UHD +^^^^^^^^^^^^^^^^^^^^^ +* `Build Guide <./build.html>`_ + +^^^^^^^^^^^^^^^^^^^^^ +Application Notes +^^^^^^^^^^^^^^^^^^^^^ +* `General Application Notes <./general.html>`_ +* `Device Identification Notes <./identification.html>`_ +* `Firmware and FPGA Image Notes <./images.html>`_ +* `USRP1 Application Notes <./usrp1.html>`_ +* `USRP2 Application Notes <./usrp2.html>`_ +* `USRP-N2XX Series Application Notes <./usrp2.html>`_ +* `USRP-B1XX Series Application Notes <./usrp_b1xx.html>`_ +* `USRP-E1XX Series Application Notes <./usrp_e1xx.html>`_ +* `Daughterboard Application Notes <./dboards.html>`_ +* `Transport Application Notes <./transport.html>`_ +* `Synchronization Application Notes <./sync.html>`_ +* `Internal GPSDO Application Notes <./gpsdo.html>`_ +* `Calibration Application Notes <./calibration.html>`_ + +^^^^^^^^^^^^^^^^^^^^^ +API Documentation +^^^^^^^^^^^^^^^^^^^^^ +* `Doxygen <./../../doxygen/html/index.html>`_ +* `Using the API <./coding.html>`_ +* `Device streaming <./stream.html>`_ + diff --git a/host/docs/stream.rst b/host/docs/stream.rst new file mode 100644 index 000000000..9ffec22e5 --- /dev/null +++ b/host/docs/stream.rst @@ -0,0 +1,59 @@ +======================================================================== +UHD - Device streaming +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Introduction to streaming +------------------------------------------------------------------------ +The concept of streaming refers to the transportation of samples between host and device. +A stream is an object that facilitates streaming between host application and device. +A RX stream allows the user to receive samples from the device. +A TX stream allows the user to transmit samples to the device. + +------------------------------------------------------------------------ +Link layer encapsulation +------------------------------------------------------------------------ +The VITA49 standard provides encapsulation for sample data across a link layer. +On all generation2 hardware, samples are encapsulated into VRT IF data packets. +These packets also provide sample decoration such as stream time and burst flags. +Sample decoration is exposed to the user in the form of RX and TX metadata structs. + +The length of an IF data packet can be limited by several factors: + +* MTU of the link layer - network card, network switch +* Buffering on the host - frame size in a ring buffer +* Buffering on the device - size of BRAM FIFOs + +------------------------------------------------------------------------ +Data types +------------------------------------------------------------------------ +There are two important data types to consider when streaming: + +* The data type of the samples used on the host for processing +* The data type of the samples sent through the link-layer + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The host/CPU data type +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The host data type refers to the format of samples used in the host for baseband processing. +Typically, the data type is complex baseband such as normalized complex-float32 or complex-int16. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The link-layer data type +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The link-layer or "over-the-wire" data type refers to the format of the samples sent through the link. +Typically, this data type is complex-int16. +However, To increase throughput over the link-layer, +at the expense of precision, complex-int8 may be used. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The user may request arbitrary combinations of host and link data types; +however, not all combinations are supported. +The user may register custom data type formats and conversion routines. +See uhd/convert.hpp for futher documentation. + +TODO provide example of convert API diff --git a/host/docs/style.css b/host/docs/style.css new file mode 100644 index 000000000..bf97bf007 --- /dev/null +++ b/host/docs/style.css @@ -0,0 +1,102 @@ +body{ +font-family:Arial, Helvetica, sans-serif; +font-size:11pt; +color:black; +background-color:white; +width:90%; +margin:0 auto 0 auto; +} + +div.document div.contents{ +border:1px solid #333333; +padding:10px 30px 10px 10px; +margin-left:50px; +color:inherit; +background-color:#FCFCFC; +display:inline-block; +} + +div.document p.topic-title{ +font-weight:bold; +} + +div.document a:link, div.document a:visited{ +color:#236B8E; +background-color:inherit; +text-decoration:none; +} + +div.document a:hover{ +color:#4985D6; +background-color:inherit; +text-decoration:none; +} + +div.document h1.title{ +font-size:150%; +border-left:1px solid #333333; +border-bottom:1px solid #333333; +text-align:left; +padding:10px 0px 10px 10px; +margin:10px 5px 20px 5px; +color:#333333; +background-color:inherit; +} + +div.document h2.subtitle, div.section h1{ +margin-top:50px; +border-bottom:1px solid #333333; +font-size:140%; +text-align:center; +padding:20px 0px 10px 0px; +color:#333333; +background-color:inherit; +} + +div.section h2{ +font-size:110%; +text-align:left; +padding:15px 0px 5px 0px; +text-decoration:underline; +color:#333333; +background-color:inherit; +} + +div.document pre.literal-block{ +border:1px inset #333333; +padding:5px; +margin:10px 5px 10px 5px; +color:inherit; +background-color:#FCFCFC; +font-size:90%; +} + +div.document table{ +padding:5px; +font-size:95%; +} + +div.document th{ +padding:3px 7px 3px 7px; +border:1px solid #333333; +text-align:center; +color:inherit; +background-color:#ECECEC; +} + +div.document tr{ +} + +div.document td{ +padding:3px 7px 3px 7px; +border:1px solid #333333; +text-align:center; +color:inherit; +background-color:#FCFCFC; +} + +div.footer{ +margin:50px auto 30px auto; +text-align:center; +font-size:85%; +} diff --git a/host/docs/sync.rst b/host/docs/sync.rst new file mode 100644 index 000000000..55c9f81f0 --- /dev/null +++ b/host/docs/sync.rst @@ -0,0 +1,164 @@ +======================================================================== +UHD - Synchronization Application Notes +======================================================================== + +.. contents:: Table of Contents + +The following application notes explain how to synchronize multiple USRPs +with the goal of transmitting or receiving time-aligned samples for MIMO +or other applications requiring multiple USRPs operating synchronously. + +**Note:** The following synchronization notes do not apply to USRP1, +which does not support the advanced features available in newer products. + +------------------------------------------------------------------------ +Common reference signals +------------------------------------------------------------------------ +USRPs take two reference signals in order to synchronize clocks and time: + +* A 10MHz reference to provide a single frequency reference for both devices. +* A pulse-per-second (1PPS) to synchronize the sample time across devices. +* Or, the MIMO cable transmits an encoded time message from one device to another. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +PPS and 10 MHz reference signals +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Connect the front panel SMA connectors to the reference sources. +Typically, these signals are provided by an external GPSDO. +However, some USRP models can provide these signals from an optional internal GPSDO. + +:: + + usrp->set_clock_source("external"); + usrp->set_time_source("external"); + +**Note:** +Sometimes the delay on the PPS signal will cause it to arrive inside the timing +margin the FPGA sampling clock, causing PPS edges to be separated by less or +more than 100 million cycles of the FPGA clock. If this is the case, +you can change the edge reference of the PPS signal with this special parameter: + +:: + + usrp->set_time_source("_external_"); + +**Note2:** +For users generating their own signals for the external SMA connectors, +the pulse-per-second should be clocked from the 10MHz reference. +See the application notes for your device for specific signal requirements. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +MIMO cable reference signals +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the MIMO expansion cable to share reference sources (USRP2 and N-Series). +The MIMO cable can be used synchronize one device to another device. +Users of the MIMO cable may use method 1 to synchronize multiple pairs of devices. + +:: + + usrp->set_clock_source("mimo"); + usrp->set_time_source("mimo"); + +------------------------------------------------------------------------ +Synchronizing the device time +------------------------------------------------------------------------ +The purpose of the PPS signal is to synchronously latch a time into the device. +You can use the set_time_next_pps(...) function to either initialize the sample time to 0, +or to an absolute time such as GPS time or UTC time. +For the purposes of synchronizing devices, +it doesn't matter what time you initialize to when using set_time_next_pps(...). + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Method 1 - poll the USRP time registers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +One way to initialize the PPS edge is to poll the "last PPS" time from the USRP device. +When the last PPS time increments, the user can determine that a PPS has occurred: + +:: + + const uhd::time_spec_t last_pps_time = usrp->get_time_last_pps(); + while (last_pps_time == usrp->get_time_last_pps()){ + //sleep 100 milliseconds (give or take) + } + usrp->set_time_next_pps(uhd::time_spec_t(0.0)); + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Method 2 - query the GPSDO for seconds +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Most GPSDO can be configured to output a NMEA string over the serial port once every PPS. +The user can wait for this string to determine the PPS edge, +and the user can also parse this string to determine GPS time: + +:: + + //call user's function to wait for NMEA message... + usrp->set_time_next_pps(uhd::time_spec_t(0.0)); + + -- OR -- + + //call user's function to wait for NMEA message... + //call user's function to parse the NMEA message... + usrp->set_time_next_pps(uhd::time_spec_t(gps_time+1)); + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Method 3 - internal GPSDO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +USRPs with internal GPSDOs properly configured will automatically +configure themselves to set the VITA time to current UTC time. +See the `GPSDO Application Notes <./gpsdo.html>`_ for more details. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Method 4 - MIMO cable +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +A USRP can synchronize its time to another USRP via the MIMO cable. +Unlike the other methods, this does not use a real "pulse per second". +Rather, the USRP sends an encoded time message over the MIMO cable. +The slave device will automatically synchronize to the time on the master device. +See the `MIMO Cable Application Notes <./usrp2.html#using-the-mimo-cable>`_ for more detail. + +------------------------------------------------------------------------ +Synchronizing channel phase +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Align CORDICs in the DSP +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In order to achieve phase alignment between USRPs, the CORDICS in both +devices must be aligned with respect to each other. This is easily achieved +by issuing stream commands with a time spec property, which instructs the +streaming to begin at a specified time. Since the devices are already +synchronized via the 10MHz and PPS inputs, the streaming will start at exactly +the same time on both devices. The CORDICs are reset at each start-of-burst +command, so users should ensure that every start-of-burst also has a time spec set. + +For receive, a burst is started when the user issues a stream command. This stream command should have a time spec set: +:: + + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = samps_to_recv; + stream_cmd.stream_now = false; + stream_cmd.time_spec = time_to_recv; + usrp->issue_stream_cmd(stream_cmd); + +For transmit, a burst is started when the user calls send(). The metadata should have a time spec set: +:: + + uhd::tx_metadata_t md; + md.start_of_burst = true; + md.end_of_burst = false; + md.has_time_spec = true; + md.time_spec = time_to_send; + + //send a single packet + size_t num_tx_samps = tx_streamer->send(buffs, samps_to_send, md); + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Align LOs in the front-end +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +After tuning the RF front-ends, +each local oscillator may have a random phase offset due to the dividers +in the VCO/PLL chains. This offset will remain constant after the device +has been initialized, and will remain constant until the device is closed +or re-tuned. This phase offset is typically removed by the user in MIMO +applications, using a training sequence to estimate the offset. It will +be necessary to re-align the LOs after each tune command. diff --git a/host/docs/transport.rst b/host/docs/transport.rst new file mode 100644 index 000000000..7dc465b4c --- /dev/null +++ b/host/docs/transport.rst @@ -0,0 +1,153 @@ +======================================================================== +UHD - Transport Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Introduction +------------------------------------------------------------------------ +A transport is the layer between the packet interface and a device IO interface. +The advanced user can pass optional parameters +into the underlying transport layer through the device address. +These optional parameters control how the transport object allocates memory, +resizes kernel buffers, spawns threads, etc. +When not spcified, the transport layer will use values for these parameters +that are known to perform well on a variety of systems. +The transport parameters are defined below for the various transports in the UHD: + +------------------------------------------------------------------------ +UDP transport (sockets) +------------------------------------------------------------------------ +The UDP transport is implemented with user-space sockets. +This means standard Berkeley sockets API using send()/recv(). + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Transport parameters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The following parameters can be used to alter the transport's default behavior: + +* **recv_frame_size:** The size of a single receive buffer in bytes +* **num_recv_frames:** The number of receive buffers to allocate +* **send_frame_size:** The size of a single send buffer in bytes +* **num_send_frames:** The number of send buffers to allocate + +**Note1:** +num_recv_frames does not affect performance. + +**Note2:** +num_send_frames does not affect performance. + +**Note3:** +recv_frame_size and send_frame_size can be used to +increase or decrease the maximum number of samples per packet. +The frame sizes default to an MTU of 1472 bytes per IP/UDP packet, +and may be increased if permitted by your network hardware. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Flow control parameters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The host-based flow control expects periodic update packets from the device. +These update packets inform the host of the last packet consumed by the device, +which allows the host to determine throttling conditions for the transmission of packets. +The following mechanisms affect the transmission of periodic update packets: + +* **ups_per_fifo:** The number of update packets for each FIFO's worth of bytes sent into the device +* **ups_per_sec:** The number of update packets per second (defaults to 20 updates per second) + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Resize socket buffers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +It may be useful increase the size of the socket buffers to +move the burden of buffering samples into the kernel, or to +buffer incoming samples faster than they can be processed. +However, if your application cannot process samples fast enough, +no amount of buffering can save you. +The following parameters can be used to alter socket's buffer sizes: + +* **recv_buff_size:** The desired size of the receive buffer in bytes +* **send_buff_size:** The desired size of the send buffer in bytes + +**Note:** Large send buffers tend to decrease transmit performance. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Latency Optimization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Latency is a measurement of the time it takes a sample to travel between the host and device. +Most computer hardware and software is bandwidth optimized which may negatively affect latency. +If your application has strict latency requirements, please consider the following notes: + +**Note1:** +The time taken by the device to populate a packet is proportional to the sample rate. +Therefore, to improve receive latency, configure the transport for a smaller frame size. + +**Note2:** +For overall latency improvements, +look for "Interrupt Coalescing" settings for your OS and ethernet chipset. +It seems the Intel ethernet chipsets offer fine-grained control in Linux. +Also, consult: + +* http://publib.boulder.ibm.com/infocenter/pseries/v5r3/index.jsp?topic=/com.ibm.aix.prftungd/doc/prftungd/interrupt_coal.htm + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Linux specific notes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +On linux, the maximum buffer sizes are capped by the sysctl values +**net.core.rmem_max** and **net.core.wmem_max**. +To change the maximum values, run the following commands: +:: + + sudo sysctl -w net.core.rmem_max=<new value> + sudo sysctl -w net.core.wmem_max=<new value> + +Set the values permanently by editing */etc/sysctl.conf* + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Windows specific notes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +On Windows, it is important to change the default UDP behavior such that +1500 byte packets still travel through the fast path of the sockets stack. +FastSendDatagramThreshold registry key to change documented here: + +* http://www.microsoft.com/windows/windowsmedia/howto/articles/optimize_web.aspx#appendix_e + +------------------------------------------------------------------------ +USB transport (libusb) +------------------------------------------------------------------------ +The USB transport is implemented with libusb. +Libusb provides an asynchronous API for USB bulk transfers. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Transport parameters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The following parameters can be used to alter the transport's default behavior: + +* **recv_frame_size:** The size of a single receive transfers in bytes +* **num_recv_frames:** The number of simultaneous receive transfers +* **send_frame_size:** The size of a single send transfers in bytes +* **num_send_frames:** The number of simultaneous send transfers + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Setup Udev for USB (Linux) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +On Linux, Udev handles USB plug and unplug events. +The following commands install a Udev rule +so that non-root users may access the device: + +:: + + cd <install-path>/share/uhd/utils + sudo cp uhd-usrp.rules /etc/udev/rules.d/ + sudo udevadm control --reload-rules + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Install USB driver (Windows) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +A driver package must be installed to use a USB-based product with UHD: + +* Download the driver from the UHD wiki page. +* Unzip the file into a known location. We will refer to this as the <directory>. +* Open the device manager and plug-in the USRP. You will see an unrecognized USB device in the device manager. +* Right click on the unrecognized USB device and select update/install driver software (may vary for your OS). +* In the driver installation wizard, select "browse for driver", browse to the <directory>, and select the .inf file. +* Continue through the installation wizard until the driver is installed. diff --git a/host/docs/usrp1.rst b/host/docs/usrp1.rst new file mode 100644 index 000000000..597b5b17f --- /dev/null +++ b/host/docs/usrp1.rst @@ -0,0 +1,92 @@ +======================================================================== +UHD - USRP1 Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Specify a non-standard image +------------------------------------------------------------------------ +The standard USRP1 images installer comes with two FPGA images: + * **usrp1_fpga.rbf:** 2 DDCs + 2 DUCs + * **usrp1_fpga_4rx.rbf:** 4 DDCs + 0 DUCs + +By default, the USRP1 uses the FPGA image with 2 DDCs and 2 DUCs. +However, a device address parameter can be used to override +the FPGA image selection to use an alternate or a custom FPGA image. +See the images application notes for installing custom images. + +Example device address string representations to specify non-standard firmware and/or FPGA images: + +:: + + fpga=usrp1_fpga_4rx.rbf + + -- OR -- + + fw=usrp1_fw_custom.ihx + + -- OR -- + + fpga=usrp1_fpga_4rx.rbf, fw=usrp1_fw_custom.ihx + +------------------------------------------------------------------------ +Missing and emulated features +------------------------------------------------------------------------ +The USRP1 FPGA does not have the necessary space to support the advanced +streaming capabilities that are possible with the newer USRP devices. +Some of these features are emulated in software to support the API. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +List of emulated features +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* Setting the current device time +* Getting the current device time +* Transmitting at a specific time +* Transmitting a specific number of samples +* Receiving at a specific time +* Receiving a specific number of samples +* End of burst flags for transmit/receive +* Notification on late stream command +* Notification on late transmit packet +* Notification on underflow or overflow +* Notification on broken chain error + +**Note:** +These emulated features rely on the host system's clock for timed operations, +and therefore may not have sufficient precision for the application. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +List of missing features +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* Start of burst flags for transmit/receive + +------------------------------------------------------------------------ +Hardware setup notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +External clock modification +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The USRP can be modified to accept an external clock reference instead of the 64MHz onboard reference. + * Solder SMA (LTI-SASF54GT) connector to J2001 + * Move 0 ohm 0603 resistor R2029 to R2030 + * Move 0.01uF 0603 capacitor C925 to C926 + * Remove 0.01uF 0603 capacitor C924 + +The new external clock needs to be a square wave between +7dBm and +15dBm + +After the hardware modification, +the user should burn the setting into the EEPROM, +so UHD can initialize with the correct clock rate. +Run the following commands to record the setting into the EEPROM: +:: + + cd <install-path>/share/uhd/utils + ./usrp_burn_mb_eeprom --args=<optional device args> --key=mcr --val=<rate> + +The user may override the clock rate specified in the EEPROM by using a device address: +Example: +:: + + uhd_usrp_probe --args="mcr=52e6" diff --git a/host/docs/usrp2.rst b/host/docs/usrp2.rst new file mode 100644 index 000000000..d0b1c9a87 --- /dev/null +++ b/host/docs/usrp2.rst @@ -0,0 +1,370 @@ +======================================================================== +UHD - USRP2 and N Series Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Load the images onto the SD card (USRP2 only) +------------------------------------------------------------------------ +**Warning!** +Use the usrp2_card_burner.py with caution. If you specify the wrong device node, +you could overwrite your hard drive. Make sure that --dev= specifies the SD card. + +**Warning!** +It is possible to use 3rd party SD cards with the USRP2. +However, certain types of SD cards will not interface with the CPLD: + +* Cards can be SDHC, which is not a supported interface. +* Cards can have unexpected timing characteristics. + +For these reasons, we recommend that you use the SD card that was supplied with the USRP2. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the card burner tool (unix) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + sudo <install-path>/share/uhd/utils/usrp2_card_burner_gui.py + + -- OR -- + + cd <install-path>/share/uhd/utils + sudo ./usrp2_card_burner.py --dev=/dev/sd<XXX> --fpga=<path_to_fpga_image> + sudo ./usrp2_card_burner.py --dev=/dev/sd<XXX> --fw=<path_to_firmware_image> + +Use the *--list* option to get a list of possible raw devices. +The list result will filter out disk partitions and devices too large to be the sd card. +The list option has been implemented on Linux, Mac OS X, and Windows. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the card burner tool (windows) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + <path_to_python.exe> <install-path>/share/uhd/utils/usrp2_card_burner_gui.py + +------------------------------------------------------------------------ +Load the images onto the on-board flash (USRP-N Series only) +------------------------------------------------------------------------ +The USRP-N Series can be reprogrammed over the network +to update or change the firmware and FPGA images. +When updating images, always burn both the FPGA and firmware images before power cycling. +This ensures that when the device reboots, it has a compatible set of images to boot into. + +**Note:** +Different hardware revisions require different FPGA images. +Determine the revision number from the sticker on the rear of the chassis. +Use this number to select the correct FPGA image for your device. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the net burner tool (unix) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + <install-path>/share/uhd/utils/usrp_n2xx_net_burner_gui.py + + -- OR -- + + cd <install-path>/share/uhd/utils + ./usrp_n2xx_net_burner.py --addr=<ip address> --fw=<path for firmware image> + ./usrp_n2xx_net_burner.py --addr=<ip address> --fpga=<path to FPGA image> + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the net burner tool (Windows) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + <path_to_python.exe> <install-path>/share/uhd/utils/usrp_n2xx_net_burner_gui.py + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Device recovery and bricking +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Its possible to put the device into an unusable state by loading bad images. +Fortunately, the USRP-N Series can be booted into a safe (read-only) image. +Once booted into the safe image, the user can once again load images onto the device. + +The safe-mode button is a pushbutton switch (S2) located inside the enclosure. +To boot into the safe image, hold-down the safe-mode button while power-cycling the device. +Continue to hold-down the button until the front-panel LEDs blink and remain solid. + +When in safe-mode, the USRP-N device will always have the IP address 192.168.10.2 + +------------------------------------------------------------------------ +Setup networking +------------------------------------------------------------------------ +The USRP2 only supports gigabit ethernet, +and will not work with a 10/100 Mbps interface. +However, a 10/100 Mbps interface can be connected indirectly +to a USRP2 through a gigabit ethernet switch. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Setup the host interface +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The USRP2 communicates at the IP/UDP layer over the gigabit ethernet. +The default IP address of the USRP2 is **192.168.10.2** +You will need to configure the host's ethernet interface with a static IP address to enable communication. +An address of **192.168.10.1** and a subnet mask of **255.255.255.0** is recommended. + +**Note:** +When using the UHD, if an IP address for the USRP2 is not specified, +the software will use UDP broadcast packets to locate the USRP2. +On some systems, the firewall will block UDP broadcast packets. +It is recommended that you change or disable your firewall settings. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Multiple devices per host +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +For maximum throughput, one ethernet interface per USRP2 is recommended, +although multiple devices may be connected via a gigabit ethernet switch. +In any case, each ethernet interface should have its own subnet, +and the corresponding USRP2 device should be assigned an address in that subnet. +Example: + +**Configuration for USRP2 device 0:** + +* Ethernet interface IPv4 address: 192.168.10.1 +* Ethernet interface subnet mask: 255.255.255.0 +* USRP2 device IPv4 address: 192.168.10.2 + +**Configuration for USRP2 device 1:** + +* Ethernet interface IPv4 address: 192.168.20.1 +* Ethernet interface subnet mask: 255.255.255.0 +* USRP2 device IPv4 address: 192.168.20.2 + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Change the USRP2's IP address +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +You may need to change the USRP2's IP address for several reasons: + +* to satisfy your particular network configuration +* to use multiple USRP2s on the same host computer +* to set a known IP address into USRP2 (in case you forgot) + +**Method 1:** +To change the USRP2's IP address +you must know the current address of the USRP2, +and the network must be setup properly as described above. +Run the following commands: +:: + + cd <install-path>/share/uhd/utils + ./usrp_burn_mb_eeprom --args=<optional device args> --key=ip-addr --val=192.168.10.3 + +**Method 2 (Linux Only):** +This method assumes that you do not know the IP address of your USRP2. +It uses raw ethernet packets to bypass the IP/UDP layer to communicate with the USRP2. +Run the following commands: +:: + + cd <install-path>/share/uhd/utils + sudo ./usrp2_recovery.py --ifc=eth0 --new-ip=192.168.10.3 + +------------------------------------------------------------------------ +Communication problems +------------------------------------------------------------------------ +When setting up a development machine for the first time, +you may have various difficulties communicating with the USRP device. +The following tips are designed to help narrow-down and diagnose the problem. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Firewall issues +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When the IP address is not specified, +the device discovery sends broadcast UDP packets from each ethernet interface. +Many firewalls will block the replies to these broadcast packets. +If disabling your system's firewall, +or specifying the IP address yeilds a discovered device, +then your firewall may be blocking replies to UDP broadcast packets. +If this is the case, we recommend that you disable the firewall, +or create a rule to allow all incoming packets with UDP source port 49152. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Ping the device +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The USRP will reply to icmp echo requests. +A successful ping response means that the device has booted properly, +and that it is using the expected IP address. + +:: + + ping 192.168.10.2 + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Monitor the serial output +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Read the serial port to get debug verbose from the embedded microcontroller. +The microcontroller prints useful information about IP addresses, +MAC addresses, control packets, fast-path settings, and bootloading. +Use a standard USB to 3.3v-level serial converter at 230400 baud. +Connect GND to the converter ground, and connect TXD to the converter receive. +The RXD pin can be left unconnected as this is only a one-way communication. + +* **USRP2:** Serial port located on the rear edge +* **N210:** Serial port located on the left side + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Monitor the host network traffic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use wireshark to monitor packets sent to and received from the device. + +------------------------------------------------------------------------ +Addressing the device +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Single device configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In a single-device configuration, +the USRP device must have a unique IPv4 address on the host computer. +The USRP can be identified through its IPv4 address, resolvable hostname, or by other means. +See the application notes on `device identification <./identification.html>`_. +Use this addressing scheme with the *single_usrp* interface. + +Example device address string representation for a USRP2 with IPv4 address 192.168.10.2 + +:: + + addr=192.168.10.2 + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Multiple device configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In a multi-device configuration, +each USRP device must have a unique IPv4 address on the host computer. +The device address parameter keys must be suffixed with the device index. +Each parameter key should be of the format <key><index>. +Use this addressing scheme with the *multi_usrp* interface. + +* The order in which devices are indexed corresponds to the indexing of the transmit and receive channels. +* The key indexing provides the same granularity of device identification as in the single device case. + +Example device address string representation for 2 USRP2s with IPv4 addresses 192.168.10.2 and 192.168.20.2 +:: + + addr0=192.168.10.2, addr1=192.168.20.2 + +------------------------------------------------------------------------ +Using the MIMO Cable +------------------------------------------------------------------------ +The MIMO cable allows two USRP devices to share reference clocks, +time synchronization, and the ethernet interface. +One of the devices will sink its clock and time references to the MIMO cable. +This device will be referred to as the slave, and the other device, the master. + +* The slave device acquires the clock and time references from the master device. +* The master and slave may be used individually or in a multi-device configuration. +* External clocking is optional, and should only be supplied to the master device. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Shared ethernet mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In shared ethernet mode, +only one device in the configuration can be attached to the ethernet. + +* Clock reference, time reference, and data are communicated over the MIMO cable. +* Both master and slave must have different IPv4 addresses in the same subnet. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Dual ethernet mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In dual ethernet mode, +both devices in the configuration must be attached to the ethernet. + +* Only clock reference and time reference are communicated over the MIMO cable. +* Both master and slave must have different IPv4 addresses in different subnets. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Configuring the slave +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In order for the slave to synchronize to the master over MIMO cable, +the following clock configuration must be set on the slave device: +:: + + uhd::clock_config_t clock_config; + clock_config.ref_source = uhd::clock_config_t::REF_MIMO; + clock_config.pps_source = uhd::clock_config_t::PPS_MIMO; + usrp->set_clock_config(clock_config, slave_index); + +------------------------------------------------------------------------ +Hardware setup notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Front panel LEDs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The LEDs on the front panel can be useful in debugging hardware and software issues. +The LEDs reveal the following about the state of the device: + +* **LED A:** transmitting +* **LED B:** mimo cable link +* **LED C:** receiving +* **LED D:** firmware loaded +* **LED E:** reference lock +* **LED F:** CPLD loaded + + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Ref Clock - 10MHz +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Using an external 10MHz reference clock, square wave will offer the best phase +noise performance, but sinusoid is acceptable. The reference clock requires the following power level: + +* **USRP2** 5 to 15dBm +* **N2XX** 0 to 15dBm + + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +PPS - Pulse Per Second +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Using a PPS signal for timestamp synchronization requires a square wave signal with the following amplitude: + +* **USRP2** 5Vpp +* **N2XX** 3.3 to 5Vpp + +Test the PPS input with the following app: + +* <args> are device address arguments (optional if only one USRP is on your machine) + +:: + + cd <install-path>/share/uhd/examples + ./test_pps_input --args=<args> + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Internal GPSDO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Please see the `Internal GPSDO Application Notes <./gpsdo.html>`_ +for information on configuring and using the internal GPSDO. + +------------------------------------------------------------------------ +Miscellaneous +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Available Sensors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The following sensors are available for the USRP2/N-Series motherboards; +they can be queried through the API. + +* mimo_locked - clock reference locked over the MIMO cable +* ref_locked - clock reference locked (internal/external) +* other sensors are added when the GPSDO is enabled + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Multiple RX channels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +There are two complete DDC chains in the FPGA. +In the single channel case, only one chain is ever used. +To receive from both channels, +the user must set the RX subdevice specification. +This hardware has only one daughterboard slot, +which has been aptly named slot "A". + +In the following example, a TVRX2 is installed. +Channel 0 is sourced from subdevice RX1, +channel 1 is sourced from subdevice RX2: +:: + + usrp->set_rx_subdev_spec("A:RX1 A:RX2"); diff --git a/host/docs/usrp_b1xx.rst b/host/docs/usrp_b1xx.rst new file mode 100644 index 000000000..564fb89be --- /dev/null +++ b/host/docs/usrp_b1xx.rst @@ -0,0 +1,87 @@ +======================================================================== +UHD - USRP-B1XX Series Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Specify a non-standard image +------------------------------------------------------------------------ +The UHD will automatically select the USRP B-Series images from the installed images package. +The image selection can be overridden with the "fpga" and "fw" device address parameters. + +Example device address string representations to specify non-standard images: + +:: + + fpga=usrp_b100_fpga_firmware.bin + + -- OR -- + + fw=usrp_b100_fw_firmware.ihx + +------------------------------------------------------------------------ +Changing the master clock rate +------------------------------------------------------------------------ +The master clock rate of the USRP embedded feeds both the FPGA DSP and the codec chip. +Hundreds of rates between 32MHz and 64MHz are available. +A few notable rates are: + +* 64MHz - maximum rate of the codec chip +* 61.44MHz - good for UMTS/WCDMA applications +* 52Mhz - good for GSM applications + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Set 61.44MHz - uses external VCXO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To use the 61.44MHz clock rate, the USRP embedded will require two jumpers to be moved. + +* J16 is a two pin header, remove the jumper (or leave it on pin1 only) +* J15 is a three pin header, move the jumper to (pin1, pin2) + +**Note:** See instructions below to communicate the desired clock rate into the UHD. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Set other rates - uses internal VCO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To use other clock rates, the jumpers will need to be in the default position. + +* J16 is a two pin header, move the jumper to (pin1, pin2) +* J15 is a three pin header, move the jumper to (pin2, pin3) + +To communicate the desired clock rate into the UHD, +specify the a special device address argument, +where the key is "master_clock_rate" and the value is a rate in Hz. +Example: +:: + + uhd_usrp_probe --args="master_clock_rate=52e6" + +------------------------------------------------------------------------ +Hardware setup notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Front panel LEDs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The LEDs on the front panel can be useful in debugging hardware and software issues. +The LEDs reveal the following about the state of the device: + +* **LED A:** transmitting +* **LED B:** fpga loaded +* **LED C:** receiving +* **LED D:** fpga loaded +* **LED E:** reference lock +* **LED F:** board power + +------------------------------------------------------------------------ +Miscellaneous +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Available Sensors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The following sensors are available; +they can be queried through the API. + +* ref_locked - clock reference locked (internal/external) diff --git a/host/docs/usrp_e1xx.rst b/host/docs/usrp_e1xx.rst new file mode 100644 index 000000000..ef1e22b3a --- /dev/null +++ b/host/docs/usrp_e1xx.rst @@ -0,0 +1,136 @@ +======================================================================== +UHD - USRP-E1XX Series Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Specify a non-standard image +------------------------------------------------------------------------ +UHD will automatically select the USRP-Embedded FPGA image from the +installed images package. The FPGA image selection can be overridden with the +"fpga" device address parameter. + +Example device address string representations to specify non-standard FPGA +image: + +:: + + fpga=usrp_e100_custom.bin + +------------------------------------------------------------------------ +Changing the master clock rate +------------------------------------------------------------------------ +The master clock rate of the USRP-Embedded feeds both the FPGA DSP and the codec +chip. Hundreds of rates between 32MHz and 64MHz are available. A few notable +rates are: + +* 64MHz - maximum rate of the codec chip +* 61.44MHz - good for UMTS/WCDMA applications +* 52Mhz - good for GSM applications + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Set 61.44MHz - uses external VCXO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To use the 61.44MHz clock rate with the USRP-Embedded, two jumpers must be moved +on the device. + +* J16 is a two pin header, remove the jumper (or leave it on pin1 only) +* J15 is a three pin header, move the jumper to (pin1, pin2) + +**Note:** See instructions below to communicate the desired clock rate into the +UHD. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Set other rates - uses internal VCO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To use other clock rates, the jumpers will need to be in the default position. + +* J16 is a two pin header, move the jumper to (pin1, pin2) +* J15 is a three pin header, move the jumper to (pin2, pin3) + +To communicate the desired clock rate into the UHD, +specify the a special device address argument, +where the key is "master_clock_rate" and the value is a rate in Hz. +Example: +:: + + uhd_usrp_probe --args="master_clock_rate=52e6" + +------------------------------------------------------------------------ +Clock Synchronization +------------------------------------------------------------------------ + + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Ref Clock - 10MHz +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The E1xx has a 10MHz TCXO which can be used to discipline the flexible clocking +by selecting REF_INT for the clock_config_t. + +Alternately, an external 10MHz reference clock can be supplied by soldering +a connector. + +* Connector J10 (REF_IN) needs MCX connector WM5541-ND or similar +* Square wave will offer the best phase noise performance, but sinusoid is acceptable +* Power level: 0 to 15dBm +* Select REF_SMA in clock_config_t + + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +PPS - Pulse Per Second +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +An exteral PPS signal for timestamp synchronization can be supplied by soldering +a connector. + +* Connector J13 (PPS) needs MCX connector WM5541-ND or similar +* Requires a square wave signal +* Amplitude: 3.3 to 5Vpp + +Test the PPS input with the following app: + +* <args> are device address arguments (optional if only one USRP is on your machine) + +:: + + cd <install-path>/share/uhd/examples + ./test_pps_input --args=<args> + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Internal GPSDO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Please see the `Internal GPSDO Application Notes <./gpsdo.html>`_ +for information on configuring and using the internal GPSDO. + +UHD will always try to detect an installed GPSDO at runtime. +There is not a special EEPROM value to burn for GPSDO detection. + +------------------------------------------------------------------------ +Hardware setup notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Front panel LEDs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The LEDs on the front panel can be useful in debugging hardware and software +issues. The LEDs reveal the following about the state of the device: + +* **LED A:** transmitting +* **LED B:** fpga loaded +* **LED C:** receiving +* **LED D:** fpga loaded +* **LED E:** reference lock +* **LED F:** board power + +------------------------------------------------------------------------ +Miscellaneous +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Available Sensors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The following sensors are available; +they can be queried through the API. + +* ref_locked - clock reference locked (internal/external) +* other sensors are added when the GPSDO is enabled diff --git a/host/examples/CMakeLists.txt b/host/examples/CMakeLists.txt new file mode 100644 index 000000000..3c9a3880a --- /dev/null +++ b/host/examples/CMakeLists.txt @@ -0,0 +1,55 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# example applications +######################################################################## +SET(example_sources + benchmark_rate.cpp + network_relay.cpp + rx_multi_samples.cpp + rx_samples_to_file.cpp + rx_samples_to_udp.cpp + rx_timed_samples.cpp + test_messages.cpp + test_pps_input.cpp + tx_bursts.cpp + tx_samples_from_file.cpp + tx_timed_samples.cpp + tx_waveforms.cpp + latency_test.cpp +) + +#for each source: build an executable and install +FOREACH(example_source ${example_sources}) + GET_FILENAME_COMPONENT(example_name ${example_source} NAME_WE) + ADD_EXECUTABLE(${example_name} ${example_source}) + TARGET_LINK_LIBRARIES(${example_name} uhd) + INSTALL(TARGETS ${example_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/examples COMPONENT examples) +ENDFOREACH(example_source) + +######################################################################## +# ASCII Art DFT - requires curses, so this part is optional +######################################################################## +FIND_PACKAGE(Curses) + +IF(CURSES_FOUND) + INCLUDE_DIRECTORIES(${CURSES_INCLUDE_DIR}) + ADD_EXECUTABLE(rx_ascii_art_dft rx_ascii_art_dft.cpp) + TARGET_LINK_LIBRARIES(rx_ascii_art_dft uhd ${CURSES_LIBRARIES}) + INSTALL(TARGETS rx_ascii_art_dft RUNTIME DESTINATION ${PKG_LIB_DIR}/examples COMPONENT examples) +ENDIF(CURSES_FOUND) diff --git a/host/examples/ascii_art_dft.hpp b/host/examples/ascii_art_dft.hpp new file mode 100644 index 000000000..ee2267c2d --- /dev/null +++ b/host/examples/ascii_art_dft.hpp @@ -0,0 +1,320 @@ +// +// ASCII Art DFT Plotter - Josh Blum +// + +#ifndef ASCII_ART_DFT_HPP +#define ASCII_ART_DFT_HPP + +#include <string> +#include <cstddef> +#include <vector> +#include <complex> +#include <stdexcept> + +namespace acsii_art_dft{ + + //! Type produced by the log power DFT function + typedef std::vector<float> log_pwr_dft_type; + + /*! + * Get a logarithmic power DFT of the input samples. + * Samples are expected to be in the range [-1.0, 1.0]. + * \param samps a pointer to an array of complex samples + * \param nsamps the number of samples in the array + * \return a real range of DFT bins in units of dB + */ + template <typename T> log_pwr_dft_type log_pwr_dft( + const std::complex<T> *samps, size_t nsamps + ); + + /*! + * Convert a DFT to a piroundable ascii plot. + * \param dft the log power dft bins + * \param width the frame width in characters + * \param height the frame height in characters + * \param samp_rate the sample rate in Sps + * \param dc_freq the DC frequency in Hz + * \param dyn_rng the dynamic range in dB + * \param ref_lvl the reference level in dB + * \return the plot as an ascii string + */ + std::string dft_to_plot( + const log_pwr_dft_type &dft, + size_t width, + size_t height, + double samp_rate, + double dc_freq, + float dyn_rng, + float ref_lvl + ); + +} //namespace ascii_dft + +/*********************************************************************** + * Implementation includes + **********************************************************************/ +#include <cmath> +#include <sstream> +#include <algorithm> + +/*********************************************************************** + * Helper functions + **********************************************************************/ +namespace {/*anon*/ + + static const double pi = double(std::acos(-1.0)); + + //! Round a floating-point value to the nearest integer + template <typename T> int iround(T val){ + return (val > 0)? int(val + 0.5) : int(val - 0.5); + } + + //! Pick the closest number that is nice to display + template <typename T> T to_clean_num(const T num){ + if (num == 0) return 0; + const T pow10 = std::pow(T(10), int(std::floor(std::log10(std::abs(num))))); + const T norm = std::abs(num)/pow10; + static const int cleans[] = {1, 2, 5, 10}; + int clean = cleans[0]; + for (size_t i = 1; i < sizeof(cleans)/sizeof(cleans[0]); i++){ + if (std::abs(norm - cleans[i]) < std::abs(norm - clean)) + clean = cleans[i]; + } + return ((num < 0)? -1 : 1)*clean*pow10; + } + + //! Compute an FFT with pre-computed factors using Cooley-Tukey + template <typename T> std::complex<T> ct_fft_f( + const std::complex<T> *samps, size_t nsamps, + const std::complex<T> *factors, + size_t start = 0, size_t step = 1 + ){ + if (nsamps == 1) return samps[start]; + std::complex<T> E_k = ct_fft_f(samps, nsamps/2, factors+1, start, step*2); + std::complex<T> O_k = ct_fft_f(samps, nsamps/2, factors+1, start+step, step*2); + return E_k + factors[0]*O_k; + } + + //! Compute an FFT for a particular bin k using Cooley-Tukey + template <typename T> std::complex<T> ct_fft_k( + const std::complex<T> *samps, size_t nsamps, size_t k + ){ + //pre-compute the factors to use in Cooley-Tukey + std::vector<std::complex<T> > factors; + for (size_t N = nsamps; N != 0; N /= 2){ + factors.push_back(std::exp(std::complex<T>(0, T(-2*pi*k/N)))); + } + return ct_fft_f(samps, nsamps, &factors.front()); + } + + //! Helper class to build a DFT plot frame + class frame_type{ + public: + frame_type(size_t width, size_t height): + _frame(width-1, std::vector<char>(height, ' ')) + { + /* NOP */ + } + + //accessors to parts of the frame + char &get_plot(size_t b, size_t z){return _frame.at(b+albl_w).at(z+flbl_h);} + char &get_albl(size_t b, size_t z){return _frame.at(b) .at(z+flbl_h);} + char &get_ulbl(size_t b) {return _frame.at(b) .at(flbl_h-1);} + char &get_flbl(size_t b) {return _frame.at(b+albl_w).at(flbl_h-1);} + + //dimension accessors + size_t get_plot_h(void) const{return _frame.front().size() - flbl_h;} + size_t get_plot_w(void) const{return _frame.size() - albl_w;} + size_t get_albl_w(void) const{return albl_w;} + + std::string to_string(void){ + std::stringstream frame_ss; + for (size_t z = 0; z < _frame.front().size(); z++){ + for (size_t b = 0; b < _frame.size(); b++){ + frame_ss << _frame[b][_frame[b].size()-z-1]; + } + frame_ss << std::endl; + } + return frame_ss.str(); + } + + private: + static const size_t albl_w = 6, flbl_h = 1; + std::vector<std::vector<char> > _frame; + }; + +} //namespace /*anon*/ + +/*********************************************************************** + * Implementation code + **********************************************************************/ +namespace acsii_art_dft{ + + //! skip constants for amplitude and frequency labels + static const size_t albl_skip = 5, flbl_skip = 20; + + template <typename T> log_pwr_dft_type log_pwr_dft( + const std::complex<T> *samps, size_t nsamps + ){ + if (nsamps & (nsamps - 1)) + throw std::runtime_error("num samps is not a power of 2"); + + //compute the window + double win_pwr = 0; + std::vector<std::complex<T> > win_samps; + for(size_t n = 0; n < nsamps; n++){ + //double w_n = 1; + //double w_n = 0.54 //hamming window + // -0.46*std::cos(2*pi*n/(nsamps-1)) + //; + double w_n = 0.35875 //blackman-harris window + -0.48829*std::cos(2*pi*n/(nsamps-1)) + +0.14128*std::cos(4*pi*n/(nsamps-1)) + -0.01168*std::cos(6*pi*n/(nsamps-1)) + ; + //double w_n = 1 // flat top window + // -1.930*std::cos(2*pi*n/(nsamps-1)) + // +1.290*std::cos(4*pi*n/(nsamps-1)) + // -0.388*std::cos(6*pi*n/(nsamps-1)) + // +0.032*std::cos(8*pi*n/(nsamps-1)) + //; + win_samps.push_back(T(w_n)*samps[n]); + win_pwr += w_n*w_n; + } + + //compute the log-power dft + log_pwr_dft_type log_pwr_dft; + for(size_t k = 0; k < nsamps; k++){ + std::complex<T> dft_k = ct_fft_k(&win_samps.front(), nsamps, k); + log_pwr_dft.push_back(float( + + 20*std::log10(std::abs(dft_k)) + - 20*std::log10(T(nsamps)) + - 10*std::log10(win_pwr/nsamps) + + 3 + )); + } + + return log_pwr_dft; + } + + std::string dft_to_plot( + const log_pwr_dft_type &dft_, + size_t width, + size_t height, + double samp_rate, + double dc_freq, + float dyn_rng, + float ref_lvl + ){ + frame_type frame(width, height); //fill this frame + + //re-order the dft so dc in in the center + const size_t num_bins = dft_.size() - 1 + dft_.size()%2; //make it odd + log_pwr_dft_type dft(num_bins); + for (size_t n = 0; n < num_bins; n++){ + dft[n] = dft_[(n + num_bins/2)%num_bins]; + } + + //fill the plot with dft bins + for (size_t b = 0; b < frame.get_plot_w(); b++){ + //indexes from the dft to grab for the plot + const size_t n_start = std::max(iround(double(b-0.5)*(num_bins-1)/(frame.get_plot_w()-1)), 0); + const size_t n_stop = std::min(iround(double(b+0.5)*(num_bins-1)/(frame.get_plot_w()-1)), int(num_bins)); + + //calculate val as the max across points + float val = dft.at(n_start); + for (size_t n = n_start; n < n_stop; n++) val = std::max(val, dft.at(n)); + + const float scaled = (val - (ref_lvl - dyn_rng))*(frame.get_plot_h()-1)/dyn_rng; + for (size_t z = 0; z < frame.get_plot_h(); z++){ + static const std::string syms(".:!|"); + if (scaled-z > 1) frame.get_plot(b, z) = syms.at(syms.size()-1); + else if (scaled-z > 0) frame.get_plot(b, z) = syms.at(size_t((scaled-z)*syms.size())); + } + } + + //create vertical amplitude labels + const float db_step = to_clean_num(dyn_rng/(frame.get_plot_h()-1)*albl_skip); + for ( + float db = db_step*(int((ref_lvl - dyn_rng)/db_step)); + db <= db_step*(int(ref_lvl/db_step)); + db += db_step + ){ + const int z = iround((db - (ref_lvl - dyn_rng))*(frame.get_plot_h()-1)/dyn_rng); + if (z < 0 or size_t(z) >= frame.get_plot_h()) continue; + std::stringstream ss; ss << db; std::string lbl = ss.str(); + for (size_t i = 0; i < lbl.size() and i < frame.get_albl_w(); i++){ + frame.get_albl(i, z) = lbl[i]; + } + } + + //create vertical units label + std::string ulbl = "dBfs"; + for (size_t i = 0; i < ulbl.size(); i++){ + frame.get_ulbl(i+1) = ulbl[i]; + } + + //create horizontal frequency labels + const double f_step = to_clean_num(samp_rate/frame.get_plot_w()*flbl_skip); + for ( + double freq = f_step*int((-samp_rate/2/f_step)); + freq <= f_step*int((+samp_rate/2/f_step)); + freq += f_step + ){ + const int b = iround((freq + samp_rate/2)*(frame.get_plot_w()-1)/samp_rate); + std::stringstream ss; ss << (freq+dc_freq)/1e6 << "MHz"; std::string lbl = ss.str(); + if (b < int(lbl.size()/2) or b + lbl.size() - lbl.size()/2 >= frame.get_plot_w()) continue; + for (size_t i = 0; i < lbl.size(); i++){ + frame.get_flbl(b + i - lbl.size()/2) = lbl[i]; + } + } + + return frame.to_string(); + } +} //namespace ascii_dft + +#endif /*ASCII_ART_DFT_HPP*/ + +/* + +//example main function to test the dft + +#include <iostream> +#include <cstdlib> +#include <curses.h> + +int main(void){ + initscr(); + + while (true){ + clear(); + + std::vector<std::complex<float> > samples; + for(size_t i = 0; i < 512; i++){ + samples.push_back(std::complex<float>( + float(std::rand() - RAND_MAX/2)/(RAND_MAX)/4, + float(std::rand() - RAND_MAX/2)/(RAND_MAX)/4 + )); + samples[i] += 0.5*std::sin(i*3.14/2) + 0.7; + } + + acsii_art_dft::log_pwr_dft_type dft; + dft = acsii_art_dft::log_pwr_dft(&samples.front(), samples.size()); + + printw("%s", acsii_art_dft::dft_to_plot( + dft, COLS, LINES, + 12.5e4, 2.45e9, + 60, 0 + ).c_str()); + + sleep(1); + } + + + endwin(); + std::cout << "here\n"; + return 0; +} + +*/ + diff --git a/host/examples/benchmark_rate.cpp b/host/examples/benchmark_rate.cpp new file mode 100644 index 000000000..8f00e25de --- /dev/null +++ b/host/examples/benchmark_rate.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +/*********************************************************************** + * Test result variables + **********************************************************************/ +unsigned long long num_overflows = 0; +unsigned long long num_underflows = 0; +unsigned long long num_rx_samps = 0; +unsigned long long num_tx_samps = 0; +unsigned long long num_dropped_samps = 0; +unsigned long long num_seq_errors = 0; + +/*********************************************************************** + * Benchmark RX Rate + **********************************************************************/ +void benchmark_rx_rate(uhd::usrp::multi_usrp::sptr usrp, const std::string &rx_otw){ + uhd::set_thread_priority_safe(); + + //create a receive streamer + uhd::stream_args_t stream_args("fc32", rx_otw); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //print pre-test summary + std::cout << boost::format( + "Testing receive rate %f Msps" + ) % (usrp->get_rx_rate()/1e6) << std::endl; + + //setup variables and allocate buffer + uhd::rx_metadata_t md; + const size_t max_samps_per_packet = rx_stream->get_max_num_samps(); + std::vector<std::complex<float> > buff(max_samps_per_packet); + bool had_an_overflow = false; + uhd::time_spec_t last_time; + const double rate = usrp->get_rx_rate(); + + usrp->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + while (not boost::this_thread::interruption_requested()){ + num_rx_samps += rx_stream->recv( + &buff.front(), buff.size(), md + ); + + //handle the error codes + switch(md.error_code){ + case uhd::rx_metadata_t::ERROR_CODE_NONE: + if (had_an_overflow){ + had_an_overflow = false; + num_dropped_samps += boost::math::iround((md.time_spec - last_time).get_real_secs()*rate); + } + break; + + case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: + had_an_overflow = true; + last_time = md.time_spec; + num_overflows++; + break; + + default: + std::cerr << "Error code: " << md.error_code << std::endl; + std::cerr << "Unexpected error on recv, continuing..." << std::endl; + break; + } + + } + usrp->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); +} + +/*********************************************************************** + * Benchmark TX Rate + **********************************************************************/ +void benchmark_tx_rate(uhd::usrp::multi_usrp::sptr usrp, const std::string &tx_otw){ + uhd::set_thread_priority_safe(); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32", tx_otw); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //print pre-test summary + std::cout << boost::format( + "Testing transmit rate %f Msps" + ) % (usrp->get_tx_rate()/1e6) << std::endl; + + //setup variables and allocate buffer + uhd::tx_metadata_t md; + md.has_time_spec = false; + const size_t max_samps_per_packet = tx_stream->get_max_num_samps(); + std::vector<std::complex<float> > buff(max_samps_per_packet); + + while (not boost::this_thread::interruption_requested()){ + num_tx_samps += tx_stream->send(&buff.front(), buff.size(), md); + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); +} + +void benchmark_tx_rate_async_helper(uhd::usrp::multi_usrp::sptr usrp){ + //setup variables and allocate buffer + uhd::async_metadata_t async_md; + + while (not boost::this_thread::interruption_requested()){ + + if (not usrp->get_device()->recv_async_msg(async_md)) continue; + + //handle the error codes + switch(async_md.event_code){ + case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: + return; + + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: + num_underflows++; + break; + + case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: + case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: + num_seq_errors++; + break; + + default: + std::cerr << "Event code: " << async_md.event_code << std::endl; + std::cerr << "Unexpected event on async recv, continuing..." << std::endl; + break; + } + } +} + +/*********************************************************************** + * Main code + dispatcher + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + double duration; + double rx_rate, tx_rate; + std::string rx_otw, tx_otw; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ("duration", po::value<double>(&duration)->default_value(10.0), "duration for the test in seconds") + ("rx_rate", po::value<double>(&rx_rate), "specify to perform a RX rate test (sps)") + ("tx_rate", po::value<double>(&tx_rate), "specify to perform a TX rate test (sps)") + ("rx_otw", po::value<std::string>(&rx_otw)->default_value("sc16"), "specify the over-the-wire sample mode for RX") + ("tx_otw", po::value<std::string>(&tx_otw)->default_value("sc16"), "specify the over-the-wire sample mode for TX") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help") or (vm.count("rx_rate") + vm.count("tx_rate")) == 0){ + std::cout << boost::format("UHD Benchmark Rate %s") % desc << std::endl; + std::cout << + " Specify --rx_rate for a receive-only test.\n" + " Specify --tx_rate for a transmit-only test.\n" + " Specify both options for a full-duplex test.\n" + << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + uhd::device_addrs_t device_addrs = uhd::device::find(args); + if (not device_addrs.empty() and device_addrs.at(0).get("type", "") == "usrp1"){ + std::cerr << "*** Warning! ***" << std::endl; + std::cerr << "Benchmark results will be inaccurate on USRP1 due to insufficient features.\n" << std::endl; + } + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + boost::thread_group thread_group; + + //spawn the receive test thread + if (vm.count("rx_rate")){ + usrp->set_rx_rate(rx_rate); + thread_group.create_thread(boost::bind(&benchmark_rx_rate, usrp, rx_otw)); + } + + //spawn the transmit test thread + if (vm.count("tx_rate")){ + usrp->set_tx_rate(tx_rate); + thread_group.create_thread(boost::bind(&benchmark_tx_rate, usrp, tx_otw)); + thread_group.create_thread(boost::bind(&benchmark_tx_rate_async_helper, usrp)); + } + + //sleep for the required duration + const long secs = long(duration); + const long usecs = long((duration - secs)*1e6); + boost::this_thread::sleep(boost::posix_time::seconds(secs) + boost::posix_time::microseconds(usecs)); + + //interrupt and join the threads + thread_group.interrupt_all(); + thread_group.join_all(); + + //print summary + std::cout << std::endl << boost::format( + "Benchmark rate summary:\n" + " Num received samples: %u\n" + " Num dropped samples: %u\n" + " Num overflows detected: %u\n" + " Num transmitted samples: %u\n" + " Num sequence errors: %u\n" + " Num underflows detected: %u\n" + ) % num_rx_samps % num_dropped_samps % num_overflows % num_tx_samps % num_seq_errors % num_underflows << std::endl; + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/latency_test.cpp b/host/examples/latency_test.cpp new file mode 100644 index 000000000..518d2383a --- /dev/null +++ b/host/examples/latency_test.cpp @@ -0,0 +1,168 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + size_t nsamps; + double rate; + double rtt; + size_t nruns; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ("nsamps", po::value<size_t>(&nsamps)->default_value(100), "number of samples per run") + ("nruns", po::value<size_t>(&nruns)->default_value(1000), "number of tests to perform") + ("rtt", po::value<double>(&rtt)->default_value(0.001), "delay between receive and transmit (seconds)") + ("rate", po::value<double>(&rate)->default_value(100e6/4), "sample rate for receive and transmit (sps)") + ("verbose", "specify to enable inner-loop verbose") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD - Latency Test %s") % desc << std::endl; + std::cout << + " Latency test receives a packet at time t,\n" + " and tries to send a packet at time t + rtt,\n" + " where rtt is the round trip time sample time\n" + " from device to host and back to the device.\n" + << std::endl; + return ~0; + } + + bool verbose = vm.count("verbose") != 0; + + //create a usrp device + std::cout << std::endl; + //std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + //std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + usrp->set_time_now(uhd::time_spec_t(0.0)); + + //set the tx sample rate + usrp->set_tx_rate(rate); + std::cout << boost::format("Actual TX Rate: %f Msps...") % (usrp->get_tx_rate()/1e6) << std::endl; + + //set the rx sample rate + usrp->set_rx_rate(rate); + std::cout << boost::format("Actual RX Rate: %f Msps...") % (usrp->get_rx_rate()/1e6) << std::endl; + + //allocate a buffer to use + std::vector<std::complex<float> > buffer(nsamps); + + //create RX and TX streamers + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //initialize result counts + int time_error = 0; + int ack = 0; + int underflow = 0; + int other = 0; + + for(size_t nrun = 0; nrun < nruns; nrun++){ + + /*************************************************************** + * Issue a stream command some time in the near future + **************************************************************/ + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = buffer.size(); + stream_cmd.stream_now = false; + stream_cmd.time_spec = usrp->get_time_now() + uhd::time_spec_t(0.01); + usrp->issue_stream_cmd(stream_cmd); + + /*************************************************************** + * Receive the requested packet + **************************************************************/ + uhd::rx_metadata_t rx_md; + size_t num_rx_samps = rx_stream->recv( + &buffer.front(), buffer.size(), rx_md + ); + + if(verbose) std::cout << boost::format("Got packet: %u samples, %u full secs, %f frac secs") + % num_rx_samps % rx_md.time_spec.get_full_secs() % rx_md.time_spec.get_frac_secs() << std::endl; + + /*************************************************************** + * Transmit a packet with delta time after received packet + **************************************************************/ + uhd::tx_metadata_t tx_md; + tx_md.start_of_burst = true; + tx_md.end_of_burst = true; + tx_md.has_time_spec = true; + tx_md.time_spec = rx_md.time_spec + uhd::time_spec_t(rtt); + size_t num_tx_samps = tx_stream->send( + &buffer.front(), buffer.size(), tx_md + ); + if(verbose) std::cout << boost::format("Sent %d samples") % num_tx_samps << std::endl; + + /*************************************************************** + * Check the async messages for result + **************************************************************/ + uhd::async_metadata_t async_md; + if (not usrp->get_device()->recv_async_msg(async_md)){ + std::cout << boost::format("failed:\n Async message recv timed out.\n") << std::endl; + continue; + } + switch(async_md.event_code){ + case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: + time_error++; + break; + + case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: + ack++; + break; + + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: + underflow++; + break; + + default: + std::cerr << boost::format( + "failed:\n Got unexpected event code 0x%x.\n" + ) % async_md.event_code << std::endl; + other++; + break; + } + } + + /*************************************************************** + * Print the summary + **************************************************************/ + std::cout << boost::format("\nACK %d, UNDERFLOW %d, TIME_ERR %d, other %d") + % ack % underflow % time_error % other << std::endl; + return 0; +} diff --git a/host/examples/network_relay.cpp b/host/examples/network_relay.cpp new file mode 100644 index 000000000..a7fdb7e2e --- /dev/null +++ b/host/examples/network_relay.cpp @@ -0,0 +1,227 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <boost/program_options.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> +#include <iostream> +#include <csignal> +#include <vector> + +namespace po = boost::program_options; +namespace asio = boost::asio; + +typedef boost::shared_ptr<asio::ip::udp::socket> socket_type; + +static const size_t insane_mtu = 9000; + +boost::mutex spawn_mutex; + +#if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + //limit buffer resize on macos or it will error + const size_t rx_dsp_buff_size = size_t(1e6); +#elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) + //set to half-a-second of buffering at max rate + const size_t rx_dsp_buff_size = size_t(50e6); +#endif + +const size_t tx_dsp_buff_size = (1 << 20); + +/*********************************************************************** + * Signal handlers + **********************************************************************/ +static bool stop_signal_called = false; +void sig_int_handler(int){stop_signal_called = true;} + +static bool wait_for_recv_ready(int sock_fd){ + //setup timeval for timeout + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100000; //100ms + + //setup rset for timeout + fd_set rset; + FD_ZERO(&rset); + FD_SET(sock_fd, &rset); + + //call select with timeout on receive socket + return ::select(sock_fd+1, &rset, NULL, NULL, &tv) > 0; +} + +/*********************************************************************** + * Relay class + **********************************************************************/ +class udp_relay_type{ +public: + udp_relay_type( + const std::string &server_addr, + const std::string &client_addr, + const std::string &port, + const size_t server_rx_size = 0, + const size_t server_tx_size = 0, + const size_t client_rx_size = 0, + const size_t client_tx_size = 0 + ):_port(port){ + { + asio::ip::udp::resolver resolver(_io_service); + asio::ip::udp::resolver::query query(asio::ip::udp::v4(), server_addr, port); + asio::ip::udp::endpoint endpoint = *resolver.resolve(query); + + _server_socket = boost::shared_ptr<asio::ip::udp::socket>(new asio::ip::udp::socket(_io_service, endpoint)); + resize_buffs(_server_socket, server_rx_size, server_tx_size); + } + { + asio::ip::udp::resolver resolver(_io_service); + asio::ip::udp::resolver::query query(asio::ip::udp::v4(), client_addr, port); + asio::ip::udp::endpoint endpoint = *resolver.resolve(query); + + _client_socket = boost::shared_ptr<asio::ip::udp::socket>(new asio::ip::udp::socket(_io_service)); + _client_socket->open(asio::ip::udp::v4()); + _client_socket->connect(endpoint); + resize_buffs(_client_socket, client_rx_size, client_tx_size); + } + + std::cout << "spawning relay threads... " << _port << std::endl; + _thread_group.create_thread(boost::bind(&udp_relay_type::server_thread, this)); + spawn_mutex.lock(); + spawn_mutex.lock(); + spawn_mutex.unlock(); + _thread_group.create_thread(boost::bind(&udp_relay_type::client_thread, this)); + spawn_mutex.lock(); + spawn_mutex.lock(); + spawn_mutex.unlock(); + std::cout << " done!" << std::endl << std::endl; + } + + ~udp_relay_type(void){ + std::cout << "killing relay threads... " << _port << std::endl; + _thread_group.interrupt_all(); + _thread_group.join_all(); + std::cout << " done!" << std::endl << std::endl; + } + +private: + + static void resize_buffs(socket_type sock, const size_t rx_size, const size_t tx_size){ + if (rx_size != 0) sock->set_option(asio::socket_base::receive_buffer_size(rx_size)); + if (tx_size != 0) sock->set_option(asio::socket_base::send_buffer_size(tx_size)); + } + + void server_thread(void){ + uhd::set_thread_priority_safe(); + std::cout << " entering server_thread..." << std::endl; + spawn_mutex.unlock(); + std::vector<char> buff(insane_mtu); + while (not boost::this_thread::interruption_requested()){ + if (wait_for_recv_ready(_server_socket->native())){ + boost::mutex::scoped_lock lock(_endpoint_mutex); + const size_t len = _server_socket->receive_from(asio::buffer(&buff.front(), buff.size()), _endpoint); + lock.unlock(); + _client_socket->send(asio::buffer(&buff.front(), len)); + + //perform sequence error detection on tx dsp data (can detect bad network cards) + /* + if (_port[4] == '7'){ + static boost::uint32_t next_seq; + const boost::uint32_t this_seq = ntohl(reinterpret_cast<const boost::uint32_t *>(&buff.front())[0]); + if (next_seq != this_seq and this_seq != 0) std::cout << "S" << std::flush; + next_seq = this_seq + 1; + } + */ + } + } + std::cout << " exiting server_thread..." << std::endl; + } + + void client_thread(void){ + uhd::set_thread_priority_safe(); + std::cout << " entering client_thread..." << std::endl; + spawn_mutex.unlock(); + std::vector<char> buff(insane_mtu); + while (not boost::this_thread::interruption_requested()){ + if (wait_for_recv_ready(_client_socket->native())){ + const size_t len = _client_socket->receive(asio::buffer(&buff.front(), buff.size())); + boost::mutex::scoped_lock lock(_endpoint_mutex); + _server_socket->send_to(asio::buffer(&buff.front(), len), _endpoint); + } + } + std::cout << " exiting client_thread..." << std::endl; + } + + const std::string _port; + boost::thread_group _thread_group; + asio::io_service _io_service; + asio::ip::udp::endpoint _endpoint; + boost::mutex _endpoint_mutex; + socket_type _server_socket, _client_socket; +}; + + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string addr; + std::string bind; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("addr", po::value<std::string>(&addr), "the resolvable address of the usrp (must be specified)") + ("bind", po::value<std::string>(&bind)->default_value("0.0.0.0"), "bind the server to this network address (default: any)") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help") or not vm.count("addr")){ + std::cout + << boost::format("UHD Network Relay %s") % desc << std::endl + << "Runs a network relay between UHD on one computer and a USRP on the network.\n" + << "This example is basically for test purposes. Use at your own convenience.\n" + << std::endl; + return ~0; + } + + { + boost::shared_ptr<udp_relay_type> ctrl (new udp_relay_type(bind, addr, "49152")); + boost::shared_ptr<udp_relay_type> rxdsp0(new udp_relay_type(bind, addr, "49156", 0, tx_dsp_buff_size, rx_dsp_buff_size, 0)); + boost::shared_ptr<udp_relay_type> txdsp0(new udp_relay_type(bind, addr, "49157", tx_dsp_buff_size, 0, 0, tx_dsp_buff_size)); + boost::shared_ptr<udp_relay_type> rxdsp1(new udp_relay_type(bind, addr, "49158", 0, tx_dsp_buff_size, rx_dsp_buff_size, 0)); + boost::shared_ptr<udp_relay_type> gps (new udp_relay_type(bind, addr, "49172")); + + std::signal(SIGINT, &sig_int_handler); + std::cout << "Press Ctrl + C to stop streaming..." << std::endl; + + while (not stop_signal_called){ + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + } + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/rx_ascii_art_dft.cpp b/host/examples/rx_ascii_art_dft.cpp new file mode 100644 index 000000000..fe8fb0347 --- /dev/null +++ b/host/examples/rx_ascii_art_dft.cpp @@ -0,0 +1,198 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include "ascii_art_dft.hpp" //implementation +#include <boost/program_options.hpp> +#include <boost/thread/thread.hpp> //gets time +#include <boost/format.hpp> +#include <curses.h> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args, ant, subdev, ref; + size_t num_bins; + double rate, freq, gain, bw, frame_rate; + float ref_lvl, dyn_rng; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args") + // hardware parameters + ("rate", po::value<double>(&rate), "rate of incoming samples (sps)") + ("freq", po::value<double>(&freq), "RF center frequency in Hz") + ("gain", po::value<double>(&gain), "gain for the RF chain") + ("ant", po::value<std::string>(&ant), "daughterboard antenna selection") + ("subdev", po::value<std::string>(&subdev), "daughterboard subdevice specification") + ("bw", po::value<double>(&bw), "daughterboard IF filter bandwidth in Hz") + // display parameters + ("num-bins", po::value<size_t>(&num_bins)->default_value(512), "the number of bins in the DFT") + ("frame-rate", po::value<double>(&frame_rate)->default_value(5), "frame rate of the display (fps)") + ("ref-lvl", po::value<float>(&ref_lvl)->default_value(0), "reference level for the display (dB)") + ("dyn-rng", po::value<float>(&dyn_rng)->default_value(60), "dynamic range for the display (dB)") + ("ref", po::value<std::string>(&ref)->default_value("internal"), "waveform type (internal, external, mimo)") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help") or not vm.count("rate")){ + std::cout << boost::format("UHD RX ASCII Art DFT %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //Lock mboard clocks + usrp->set_clock_source(ref); + + //always select the subdevice first, the channel mapping affects the other settings + if (vm.count("subdev")) usrp->set_rx_subdev_spec(subdev); + + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the sample rate + if (not vm.count("rate")){ + std::cerr << "Please specify the sample rate with --rate" << std::endl; + return ~0; + } + std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_rx_rate(rate); + std::cout << boost::format("Actual RX Rate: %f Msps...") % (usrp->get_rx_rate()/1e6) << std::endl << std::endl; + + //set the center frequency + if (not vm.count("freq")){ + std::cerr << "Please specify the center frequency with --freq" << std::endl; + return ~0; + } + std::cout << boost::format("Setting RX Freq: %f MHz...") % (freq/1e6) << std::endl; + usrp->set_rx_freq(freq); + std::cout << boost::format("Actual RX Freq: %f MHz...") % (usrp->get_rx_freq()/1e6) << std::endl << std::endl; + + //set the rf gain + if (vm.count("gain")){ + std::cout << boost::format("Setting RX Gain: %f dB...") % gain << std::endl; + usrp->set_rx_gain(gain); + std::cout << boost::format("Actual RX Gain: %f dB...") % usrp->get_rx_gain() << std::endl << std::endl; + } + + //set the IF filter bandwidth + if (vm.count("bw")){ + std::cout << boost::format("Setting RX Bandwidth: %f MHz...") % bw << std::endl; + usrp->set_rx_bandwidth(bw); + std::cout << boost::format("Actual RX Bandwidth: %f MHz...") % usrp->get_rx_bandwidth() << std::endl << std::endl; + } + + //set the antenna + if (vm.count("ant")) usrp->set_rx_antenna(ant); + + boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for some setup time + + //Check Ref and LO Lock detect + std::vector<std::string> sensor_names; + sensor_names = usrp->get_rx_sensor_names(0); + if (std::find(sensor_names.begin(), sensor_names.end(), "lo_locked") != sensor_names.end()) { + uhd::sensor_value_t lo_locked = usrp->get_rx_sensor("lo_locked",0); + std::cout << boost::format("Checking RX: %s ...") % lo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(lo_locked.to_bool()); + } + sensor_names = usrp->get_mboard_sensor_names(0); + if ((ref == "mimo") and (std::find(sensor_names.begin(), sensor_names.end(), "mimo_locked") != sensor_names.end())) { + uhd::sensor_value_t mimo_locked = usrp->get_mboard_sensor("mimo_locked",0); + std::cout << boost::format("Checking RX: %s ...") % mimo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(mimo_locked.to_bool()); + } + if ((ref == "external") and (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end())) { + uhd::sensor_value_t ref_locked = usrp->get_mboard_sensor("ref_locked",0); + std::cout << boost::format("Checking RX: %s ...") % ref_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(ref_locked.to_bool()); + } + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //allocate recv buffer and metatdata + uhd::rx_metadata_t md; + std::vector<std::complex<float> > buff(num_bins); + //------------------------------------------------------------------ + //-- Initialize + //------------------------------------------------------------------ + initscr(); //curses init + usrp->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + boost::system_time next_refresh = boost::get_system_time(); + + //------------------------------------------------------------------ + //-- Main loop + //------------------------------------------------------------------ + while (true){ + //read a buffer's worth of samples every iteration + size_t num_rx_samps = rx_stream->recv( + &buff.front(), buff.size(), md + ); + if (num_rx_samps != buff.size()) continue; + + //check and update the display refresh condition + if (boost::get_system_time() < next_refresh) continue; + next_refresh = boost::get_system_time() + boost::posix_time::microseconds(long(1e6/frame_rate)); + + //calculate the dft and create the ascii art frame + acsii_art_dft::log_pwr_dft_type lpdft( + acsii_art_dft::log_pwr_dft(&buff.front(), num_rx_samps) + ); + std::string frame = acsii_art_dft::dft_to_plot( + lpdft, COLS, LINES, + usrp->get_rx_rate(), + usrp->get_rx_freq(), + dyn_rng, ref_lvl + ); + + //curses screen handling: clear and print frame + clear(); + printw("%s", frame.c_str()); + + //curses key handling: no timeout, any key to exit + timeout(0); + int ch = getch(); + if (ch != KEY_RESIZE and ch != ERR) break; + } + + //------------------------------------------------------------------ + //-- Cleanup + //------------------------------------------------------------------ + usrp->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + endwin(); //curses done + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/rx_multi_samples.cpp b/host/examples/rx_multi_samples.cpp new file mode 100644 index 000000000..42ef33d70 --- /dev/null +++ b/host/examples/rx_multi_samples.cpp @@ -0,0 +1,178 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args, sync, subdev; + double seconds_in_future; + size_t total_num_samps; + double rate; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ("secs", po::value<double>(&seconds_in_future)->default_value(1.5), "number of seconds in the future to receive") + ("nsamps", po::value<size_t>(&total_num_samps)->default_value(10000), "total number of samples to receive") + ("rate", po::value<double>(&rate)->default_value(100e6/16), "rate of incoming samples") + ("sync", po::value<std::string>(&sync)->default_value("now"), "synchronization method: now, pps, mimo") + ("subdev", po::value<std::string>(&subdev), "subdev spec (homogeneous across motherboards)") + ("dilv", "specify to disable inner-loop verbose") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD RX Multi Samples %s") % desc << std::endl; + std::cout << + " This is a demonstration of how to receive aligned data from multiple channels.\n" + " This example can receive from multiple DSPs, multiple motherboards, or both.\n" + " The MIMO cable or PPS can be used to synchronize the configuration. See --sync\n" + "\n" + " Specify --subdev to select multiple channels per motherboard.\n" + " Ex: --subdev=\"0:A 0:B\" to get 2 channels on a Basic RX.\n" + "\n" + " Specify --args to select multiple motherboards in a configuration.\n" + " Ex: --args=\"addr0=192.168.10.2, addr1=192.168.10.3\"\n" + << std::endl; + return ~0; + } + + bool verbose = vm.count("dilv") == 0; + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //always select the subdevice first, the channel mapping affects the other settings + if (vm.count("subdev")) usrp->set_rx_subdev_spec(subdev); //sets across all mboards + + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the rx sample rate (sets across all channels) + std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_rx_rate(rate); + std::cout << boost::format("Actual RX Rate: %f Msps...") % (usrp->get_rx_rate()/1e6) << std::endl << std::endl; + + std::cout << boost::format("Setting device timestamp to 0...") << std::endl; + if (sync == "now"){ + //This is not a true time lock, the devices will be off by a few RTT. + //Rather, this is just to allow for demonstration of the code below. + usrp->set_time_now(uhd::time_spec_t(0.0)); + } + else if (sync == "pps"){ + usrp->set_time_source("external"); + usrp->set_time_unknown_pps(uhd::time_spec_t(0.0)); + boost::this_thread::sleep(boost::posix_time::seconds(1)); //wait for pps sync pulse + } + else if (sync == "mimo"){ + UHD_ASSERT_THROW(usrp->get_num_mboards() == 2); + + //make mboard 1 a slave over the MIMO Cable + usrp->set_clock_source("mimo", 1); + usrp->set_time_source("mimo", 1); + + //set time on the master (mboard 0) + usrp->set_time_now(uhd::time_spec_t(0.0), 0); + + //sleep a bit while the slave locks its time to the master + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + + //create a receive streamer + //linearly map channels (index0 = channel0, index1 = channel1, ...) + uhd::stream_args_t stream_args("fc32"); //complex floats + for (size_t chan = 0; chan < usrp->get_rx_num_channels(); chan++) + stream_args.channels.push_back(chan); //linear mapping + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //setup streaming + std::cout << std::endl; + std::cout << boost::format( + "Begin streaming %u samples, %f seconds in the future..." + ) % total_num_samps % seconds_in_future << std::endl; + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = total_num_samps; + stream_cmd.stream_now = false; + stream_cmd.time_spec = uhd::time_spec_t(seconds_in_future); + usrp->issue_stream_cmd(stream_cmd); //tells all channels to stream + + //meta-data will be filled in by recv() + uhd::rx_metadata_t md; + + //allocate buffers to receive with samples (one buffer per channel) + const size_t samps_per_buff = rx_stream->get_max_num_samps(); + std::vector<std::vector<std::complex<float> > > buffs( + usrp->get_rx_num_channels(), std::vector<std::complex<float> >(samps_per_buff) + ); + + //create a vector of pointers to point to each of the channel buffers + std::vector<std::complex<float> *> buff_ptrs; + for (size_t i = 0; i < buffs.size(); i++) buff_ptrs.push_back(&buffs[i].front()); + + //the first call to recv() will block this many seconds before receiving + double timeout = seconds_in_future + 0.1; //timeout (delay before receive + padding) + + size_t num_acc_samps = 0; //number of accumulated samples + while(num_acc_samps < total_num_samps){ + //receive a single packet + size_t num_rx_samps = rx_stream->recv( + buff_ptrs, samps_per_buff, md, timeout + ); + + //use a small timeout for subsequent packets + timeout = 0.1; + + //handle the error code + if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) break; + if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){ + throw std::runtime_error(str(boost::format( + "Unexpected error code 0x%x" + ) % md.error_code)); + } + + if(verbose) std::cout << boost::format( + "Received packet: %u samples, %u full secs, %f frac secs" + ) % num_rx_samps % md.time_spec.get_full_secs() % md.time_spec.get_frac_secs() << std::endl; + + num_acc_samps += num_rx_samps; + } + + if (num_acc_samps < total_num_samps) std::cerr << "Receive timeout before all samples received..." << std::endl; + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/rx_samples_to_file.cpp b/host/examples/rx_samples_to_file.cpp new file mode 100644 index 000000000..5197bfe69 --- /dev/null +++ b/host/examples/rx_samples_to_file.cpp @@ -0,0 +1,218 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/exception.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <iostream> +#include <fstream> +#include <csignal> +#include <complex> + +namespace po = boost::program_options; + +static bool stop_signal_called = false; +void sig_int_handler(int){stop_signal_called = true;} + +template<typename samp_type> void recv_to_file( + uhd::usrp::multi_usrp::sptr usrp, + const std::string &cpu_format, + const std::string &wire_format, + const std::string &file, + size_t samps_per_buff, + int num_requested_samples +){ + int num_total_samps = 0; + //create a receive streamer + uhd::stream_args_t stream_args(cpu_format,wire_format); + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + uhd::rx_metadata_t md; + std::vector<samp_type> buff(samps_per_buff); + std::ofstream outfile(file.c_str(), std::ofstream::binary); + bool overflow_message = true; + + //setup streaming + uhd::stream_cmd_t stream_cmd((num_requested_samples == 0)? + uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS: + uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE + ); + stream_cmd.num_samps = num_requested_samples; + stream_cmd.stream_now = true; + stream_cmd.time_spec = uhd::time_spec_t(); + usrp->issue_stream_cmd(stream_cmd); + + while(not stop_signal_called and (num_requested_samples != num_total_samps or num_requested_samples == 0)){ + size_t num_rx_samps = rx_stream->recv(&buff.front(), buff.size(), md, 3.0); + + if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) { + std::cout << boost::format("Timeout while streaming") << std::endl; + break; + } + if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW){ + if (overflow_message){ + overflow_message = false; + std::cerr << boost::format( + "Got an overflow indication. Please consider the following:\n" + " Your write medium must sustain a rate of %fMB/s.\n" + " Dropped samples will not be written to the file.\n" + " Please modify this example for your purposes.\n" + " This message will not appear again.\n" + ) % (usrp->get_rx_rate()*sizeof(samp_type)/1e6); + } + continue; + } + if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){ + throw std::runtime_error(str(boost::format( + "Unexpected error code 0x%x" + ) % md.error_code)); + } + + num_total_samps += num_rx_samps; + + outfile.write((const char*)&buff.front(), num_rx_samps*sizeof(samp_type)); + } + + outfile.close(); +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args, file, type, ant, subdev, ref, wirefmt; + size_t total_num_samps, spb; + double rate, freq, gain, bw; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args") + ("file", po::value<std::string>(&file)->default_value("usrp_samples.dat"), "name of the file to write binary samples to") + ("type", po::value<std::string>(&type)->default_value("short"), "sample type: double, float, or short") + ("nsamps", po::value<size_t>(&total_num_samps)->default_value(0), "total number of samples to receive") + ("spb", po::value<size_t>(&spb)->default_value(10000), "samples per buffer") + ("rate", po::value<double>(&rate), "rate of incoming samples") + ("freq", po::value<double>(&freq), "RF center frequency in Hz") + ("gain", po::value<double>(&gain), "gain for the RF chain") + ("ant", po::value<std::string>(&ant), "daughterboard antenna selection") + ("subdev", po::value<std::string>(&subdev), "daughterboard subdevice specification") + ("bw", po::value<double>(&bw), "daughterboard IF filter bandwidth in Hz") + ("ref", po::value<std::string>(&ref)->default_value("internal"), "waveform type (internal, external, mimo)") + ("wirefmt", po::value<std::string>(&wirefmt)->default_value("sc16"), "wire format (sc8 or sc16)") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD RX samples to file %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //Lock mboard clocks + usrp->set_clock_source(ref); + + //always select the subdevice first, the channel mapping affects the other settings + if (vm.count("subdev")) usrp->set_rx_subdev_spec(subdev); + + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the sample rate + if (not vm.count("rate")){ + std::cerr << "Please specify the sample rate with --rate" << std::endl; + return ~0; + } + std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_rx_rate(rate); + std::cout << boost::format("Actual RX Rate: %f Msps...") % (usrp->get_rx_rate()/1e6) << std::endl << std::endl; + + //set the center frequency + if (not vm.count("freq")){ + std::cerr << "Please specify the center frequency with --freq" << std::endl; + return ~0; + } + std::cout << boost::format("Setting RX Freq: %f MHz...") % (freq/1e6) << std::endl; + usrp->set_rx_freq(freq); + std::cout << boost::format("Actual RX Freq: %f MHz...") % (usrp->get_rx_freq()/1e6) << std::endl << std::endl; + + //set the rf gain + if (vm.count("gain")){ + std::cout << boost::format("Setting RX Gain: %f dB...") % gain << std::endl; + usrp->set_rx_gain(gain); + std::cout << boost::format("Actual RX Gain: %f dB...") % usrp->get_rx_gain() << std::endl << std::endl; + } + + //set the IF filter bandwidth + if (vm.count("bw")){ + std::cout << boost::format("Setting RX Bandwidth: %f MHz...") % bw << std::endl; + usrp->set_rx_bandwidth(bw); + std::cout << boost::format("Actual RX Bandwidth: %f MHz...") % usrp->get_rx_bandwidth() << std::endl << std::endl; + } + + //set the antenna + if (vm.count("ant")) usrp->set_rx_antenna(ant); + + boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for some setup time + + //Check Ref and LO Lock detect + std::vector<std::string> sensor_names; + sensor_names = usrp->get_rx_sensor_names(0); + if (std::find(sensor_names.begin(), sensor_names.end(), "lo_locked") != sensor_names.end()) { + uhd::sensor_value_t lo_locked = usrp->get_rx_sensor("lo_locked",0); + std::cout << boost::format("Checking RX: %s ...") % lo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(lo_locked.to_bool()); + } + sensor_names = usrp->get_mboard_sensor_names(0); + if ((ref == "mimo") and (std::find(sensor_names.begin(), sensor_names.end(), "mimo_locked") != sensor_names.end())) { + uhd::sensor_value_t mimo_locked = usrp->get_mboard_sensor("mimo_locked",0); + std::cout << boost::format("Checking RX: %s ...") % mimo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(mimo_locked.to_bool()); + } + if ((ref == "external") and (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end())) { + uhd::sensor_value_t ref_locked = usrp->get_mboard_sensor("ref_locked",0); + std::cout << boost::format("Checking RX: %s ...") % ref_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(ref_locked.to_bool()); + } + + if (total_num_samps == 0){ + std::signal(SIGINT, &sig_int_handler); + std::cout << "Press Ctrl + C to stop streaming..." << std::endl; + } + + //recv to file + if (type == "double") recv_to_file<std::complex<double> >(usrp, "fc64", wirefmt, file, spb, total_num_samps); + else if (type == "float") recv_to_file<std::complex<float> >(usrp, "fc32", wirefmt, file, spb, total_num_samps); + else if (type == "short") recv_to_file<std::complex<short> >(usrp, "sc16", wirefmt, file, spb, total_num_samps); + else throw std::runtime_error("Unknown type " + type); + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/rx_samples_to_udp.cpp b/host/examples/rx_samples_to_udp.cpp new file mode 100644 index 000000000..42b80762f --- /dev/null +++ b/host/examples/rx_samples_to_udp.cpp @@ -0,0 +1,172 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/exception.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args, file, ant, subdev, ref; + size_t total_num_samps; + double rate, freq, gain, bw; + std::string addr, port; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args") + ("nsamps", po::value<size_t>(&total_num_samps)->default_value(1000), "total number of samples to receive") + ("rate", po::value<double>(&rate)->default_value(100e6/16), "rate of incoming samples") + ("freq", po::value<double>(&freq)->default_value(0), "rf center frequency in Hz") + ("gain", po::value<double>(&gain)->default_value(0), "gain for the RF chain") + ("ant", po::value<std::string>(&ant), "daughterboard antenna selection") + ("subdev", po::value<std::string>(&subdev), "daughterboard subdevice specification") + ("bw", po::value<double>(&bw), "daughterboard IF filter bandwidth in Hz") + ("port", po::value<std::string>(&port)->default_value("7124"), "server udp port") + ("addr", po::value<std::string>(&addr)->default_value("192.168.1.10"), "resolvable server address") + ("ref", po::value<std::string>(&ref)->default_value("internal"), "waveform type (internal, external, mimo)") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD RX to UDP %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //Lock mboard clocks + usrp->set_clock_source(ref); + + //set the rx sample rate + std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_rx_rate(rate); + std::cout << boost::format("Actual RX Rate: %f Msps...") % (usrp->get_rx_rate()/1e6) << std::endl << std::endl; + + //set the rx center frequency + std::cout << boost::format("Setting RX Freq: %f Mhz...") % (freq/1e6) << std::endl; + usrp->set_rx_freq(freq); + std::cout << boost::format("Actual RX Freq: %f Mhz...") % (usrp->get_rx_freq()/1e6) << std::endl << std::endl; + + //set the rx rf gain + std::cout << boost::format("Setting RX Gain: %f dB...") % gain << std::endl; + usrp->set_rx_gain(gain); + std::cout << boost::format("Actual RX Gain: %f dB...") % usrp->get_rx_gain() << std::endl << std::endl; + + //set the IF filter bandwidth + if (vm.count("bw")){ + std::cout << boost::format("Setting RX Bandwidth: %f MHz...") % bw << std::endl; + usrp->set_rx_bandwidth(bw); + std::cout << boost::format("Actual RX Bandwidth: %f MHz...") % usrp->get_rx_bandwidth() << std::endl << std::endl; + } + + //set the antenna + if (vm.count("ant")) usrp->set_rx_antenna(ant); + + boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for some setup time + + //Check Ref and LO Lock detect + std::vector<std::string> sensor_names; + sensor_names = usrp->get_rx_sensor_names(0); + if (std::find(sensor_names.begin(), sensor_names.end(), "lo_locked") != sensor_names.end()) { + uhd::sensor_value_t lo_locked = usrp->get_rx_sensor("lo_locked",0); + std::cout << boost::format("Checking RX: %s ...") % lo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(lo_locked.to_bool()); + } + sensor_names = usrp->get_mboard_sensor_names(0); + if ((ref == "mimo") and (std::find(sensor_names.begin(), sensor_names.end(), "mimo_locked") != sensor_names.end())) { + uhd::sensor_value_t mimo_locked = usrp->get_mboard_sensor("mimo_locked",0); + std::cout << boost::format("Checking RX: %s ...") % mimo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(mimo_locked.to_bool()); + } + if ((ref == "external") and (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end())) { + uhd::sensor_value_t ref_locked = usrp->get_mboard_sensor("ref_locked",0); + std::cout << boost::format("Checking RX: %s ...") % ref_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(ref_locked.to_bool()); + } + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //setup streaming + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = total_num_samps; + stream_cmd.stream_now = true; + usrp->issue_stream_cmd(stream_cmd); + + //loop until total number of samples reached + size_t num_acc_samps = 0; //number of accumulated samples + uhd::rx_metadata_t md; + std::vector<std::complex<float> > buff(rx_stream->get_max_num_samps()); + uhd::transport::udp_simple::sptr udp_xport = uhd::transport::udp_simple::make_connected(addr, port); + + while(num_acc_samps < total_num_samps){ + size_t num_rx_samps = rx_stream->recv( + &buff.front(), buff.size(), md + ); + + //handle the error codes + switch(md.error_code){ + case uhd::rx_metadata_t::ERROR_CODE_NONE: + break; + + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + if (num_acc_samps == 0) continue; + std::cout << boost::format( + "Got timeout before all samples received, possible packet loss, exiting loop..." + ) << std::endl; + goto done_loop; + + default: + std::cout << boost::format( + "Got error code 0x%x, exiting loop..." + ) % md.error_code << std::endl; + goto done_loop; + } + + //send complex single precision floating point samples over udp + udp_xport->send(boost::asio::buffer(buff, num_rx_samps)); + + num_acc_samps += num_rx_samps; + } done_loop: + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/rx_timed_samples.cpp b/host/examples/rx_timed_samples.cpp new file mode 100644 index 000000000..143bceb03 --- /dev/null +++ b/host/examples/rx_timed_samples.cpp @@ -0,0 +1,128 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + double seconds_in_future; + size_t total_num_samps; + double rate; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ("secs", po::value<double>(&seconds_in_future)->default_value(1.5), "number of seconds in the future to receive") + ("nsamps", po::value<size_t>(&total_num_samps)->default_value(10000), "total number of samples to receive") + ("rate", po::value<double>(&rate)->default_value(100e6/16), "rate of incoming samples") + ("dilv", "specify to disable inner-loop verbose") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD RX Timed Samples %s") % desc << std::endl; + return ~0; + } + + bool verbose = vm.count("dilv") == 0; + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the rx sample rate + std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_rx_rate(rate); + std::cout << boost::format("Actual RX Rate: %f Msps...") % (usrp->get_rx_rate()/1e6) << std::endl << std::endl; + + std::cout << boost::format("Setting device timestamp to 0...") << std::endl; + usrp->set_time_now(uhd::time_spec_t(0.0)); + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //setup streaming + std::cout << std::endl; + std::cout << boost::format( + "Begin streaming %u samples, %f seconds in the future..." + ) % total_num_samps % seconds_in_future << std::endl; + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = total_num_samps; + stream_cmd.stream_now = false; + stream_cmd.time_spec = uhd::time_spec_t(seconds_in_future); + usrp->issue_stream_cmd(stream_cmd); + + //meta-data will be filled in by recv() + uhd::rx_metadata_t md; + + //allocate buffer to receive with samples + std::vector<std::complex<float> > buff(rx_stream->get_max_num_samps()); + + //the first call to recv() will block this many seconds before receiving + double timeout = seconds_in_future + 0.1; //timeout (delay before receive + padding) + + size_t num_acc_samps = 0; //number of accumulated samples + while(num_acc_samps < total_num_samps){ + //receive a single packet + size_t num_rx_samps = rx_stream->recv( + &buff.front(), buff.size(), md, timeout, true + ); + + //use a small timeout for subsequent packets + timeout = 0.1; + + //handle the error code + if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) break; + if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){ + throw std::runtime_error(str(boost::format( + "Unexpected error code 0x%x" + ) % md.error_code)); + } + + if(verbose) std::cout << boost::format( + "Received packet: %u samples, %u full secs, %f frac secs" + ) % num_rx_samps % md.time_spec.get_full_secs() % md.time_spec.get_frac_secs() << std::endl; + + num_acc_samps += num_rx_samps; + } + + if (num_acc_samps < total_num_samps) std::cerr << "Receive timeout before all samples received..." << std::endl; + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/test_messages.cpp b/host/examples/test_messages.cpp new file mode 100644 index 000000000..afb092092 --- /dev/null +++ b/host/examples/test_messages.cpp @@ -0,0 +1,354 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/program_options.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <cstdlib> +#include <ctime> +#include <complex> +#include <iostream> + +namespace po = boost::program_options; + +/*! + * Test the late command message: + * Issue a stream command with a time that is in the past. + * We expect to get an inline late command message. + */ +bool test_late_command_message(uhd::usrp::multi_usrp::sptr usrp, uhd::rx_streamer::sptr rx_stream, uhd::tx_streamer::sptr){ + std::cout << "Test late command message... " << std::flush; + + usrp->set_time_now(uhd::time_spec_t(200.0)); //set time + + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = rx_stream->get_max_num_samps(); + stream_cmd.stream_now = false; + stream_cmd.time_spec = uhd::time_spec_t(100.0); //time in the past + usrp->issue_stream_cmd(stream_cmd); + + std::vector<std::complex<float> > buff(rx_stream->get_max_num_samps()); + uhd::rx_metadata_t md; + + const size_t nsamps = rx_stream->recv( + &buff.front(), buff.size(), md + ); + + switch(md.error_code){ + case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: + std::cout << boost::format( + "success:\n" + " Got error code late command message.\n" + ) << std::endl; + return true; + + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + std::cout << boost::format( + "failed:\n" + " Inline message recv timed out.\n" + ) << std::endl; + return false; + + default: + std::cout << boost::format( + "failed:\n" + " Got unexpected error code 0x%x, nsamps %u.\n" + ) % md.error_code % nsamps << std::endl; + return false; + } +} + +/*! + * Test the broken chain message: + * Issue a stream command with num samps and more. + * We expect to get an inline broken chain message. + */ +bool test_broken_chain_message(uhd::usrp::multi_usrp::sptr usrp, uhd::rx_streamer::sptr rx_stream, uhd::tx_streamer::sptr){ + std::cout << "Test broken chain message... " << std::flush; + + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE); + stream_cmd.stream_now = true; + stream_cmd.num_samps = rx_stream->get_max_num_samps(); + usrp->issue_stream_cmd(stream_cmd); + + std::vector<std::complex<float> > buff(rx_stream->get_max_num_samps()); + uhd::rx_metadata_t md; + + rx_stream->recv( //once for the requested samples + &buff.front(), buff.size(), md + ); + + rx_stream->recv( //again for the inline message + &buff.front(), buff.size(), md + ); + + switch(md.error_code){ + case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN: + std::cout << boost::format( + "success:\n" + " Got error code broken chain message.\n" + ) << std::endl; + return true; + + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + std::cout << boost::format( + "failed:\n" + " Inline message recv timed out.\n" + ) << std::endl; + return false; + + default: + std::cout << boost::format( + "failed:\n" + " Got unexpected error code 0x%x.\n" + ) % md.error_code << std::endl; + return false; + } +} + +/*! + * Test the burst ack message: + * Send a burst of many samples that will fragment internally. + * We expect to get an burst ack async message. + */ +bool test_burst_ack_message(uhd::usrp::multi_usrp::sptr usrp, uhd::rx_streamer::sptr, uhd::tx_streamer::sptr tx_stream){ + std::cout << "Test burst ack message... " << std::flush; + + uhd::tx_metadata_t md; + md.start_of_burst = true; + md.end_of_burst = true; + md.has_time_spec = false; + + //3 times max-sps guarantees a SOB, no burst, and EOB packet + std::vector<std::complex<float> > buff(tx_stream->get_max_num_samps()*3); + + tx_stream->send( + &buff.front(), buff.size(), md + ); + + uhd::async_metadata_t async_md; + if (not usrp->get_device()->recv_async_msg(async_md)){ + std::cout << boost::format( + "failed:\n" + " Async message recv timed out.\n" + ) << std::endl; + return false; + } + + switch(async_md.event_code){ + case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: + std::cout << boost::format( + "success:\n" + " Got event code burst ack message.\n" + ) << std::endl; + return true; + + default: + std::cout << boost::format( + "failed:\n" + " Got unexpected event code 0x%x.\n" + ) % async_md.event_code << std::endl; + return false; + } +} + +/*! + * Test the underflow message: + * Send a start of burst packet with no following end of burst. + * We expect to get an underflow(within a burst) async message. + */ +bool test_underflow_message(uhd::usrp::multi_usrp::sptr usrp, uhd::rx_streamer::sptr, uhd::tx_streamer::sptr tx_stream){ + std::cout << "Test underflow message... " << std::flush; + + uhd::tx_metadata_t md; + md.start_of_burst = true; + md.end_of_burst = false; + md.has_time_spec = false; + + tx_stream->send("", 0, md); + + uhd::async_metadata_t async_md; + if (not usrp->get_device()->recv_async_msg(async_md, 1)){ + std::cout << boost::format( + "failed:\n" + " Async message recv timed out.\n" + ) << std::endl; + return false; + } + + switch(async_md.event_code){ + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: + std::cout << boost::format( + "success:\n" + " Got event code underflow message.\n" + ) << std::endl; + return true; + + default: + std::cout << boost::format( + "failed:\n" + " Got unexpected event code 0x%x.\n" + ) % async_md.event_code << std::endl; + return false; + } +} + +/*! + * Test the time error message: + * Send a burst packet that occurs at a time in the past. + * We expect to get a time error async message. + */ +bool test_time_error_message(uhd::usrp::multi_usrp::sptr usrp, uhd::rx_streamer::sptr, uhd::tx_streamer::sptr tx_stream){ + std::cout << "Test time error message... " << std::flush; + + uhd::tx_metadata_t md; + md.start_of_burst = true; + md.end_of_burst = true; + md.has_time_spec = true; + md.time_spec = uhd::time_spec_t(100.0); //send at 100s + + usrp->set_time_now(uhd::time_spec_t(200.0)); //time at 200s + + tx_stream->send("", 0, md); + + uhd::async_metadata_t async_md; + if (not usrp->get_device()->recv_async_msg(async_md)){ + std::cout << boost::format( + "failed:\n" + " Async message recv timed out.\n" + ) << std::endl; + return false; + } + + switch(async_md.event_code){ + case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: + std::cout << boost::format( + "success:\n" + " Got event code time error message.\n" + ) << std::endl; + return true; + + default: + std::cout << boost::format( + "failed:\n" + " Got unexpected event code 0x%x.\n" + ) % async_md.event_code << std::endl; + return false; + } +} + +void flush_async(uhd::usrp::multi_usrp::sptr usrp){ + uhd::async_metadata_t async_md; + while (usrp->get_device()->recv_async_msg(async_md)){} +} + +void flush_recv(uhd::rx_streamer::sptr rx_stream){ + std::vector<std::complex<float> > buff(rx_stream->get_max_num_samps()); + uhd::rx_metadata_t md; + + do{ + rx_stream->recv(&buff.front(), buff.size(), md); + } while (md.error_code != uhd::rx_metadata_t::ERROR_CODE_TIMEOUT); +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + size_t ntests; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args") + ("ntests", po::value<size_t>(&ntests)->default_value(50), "number of tests to run") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD Test Messages %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //create RX and TX streamers + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //------------------------------------------------------------------ + // begin messages test + //------------------------------------------------------------------ + static const uhd::dict<std::string, boost::function<bool(uhd::usrp::multi_usrp::sptr, uhd::rx_streamer::sptr, uhd::tx_streamer::sptr)> > + tests = boost::assign::map_list_of + ("Test Burst ACK ", &test_burst_ack_message) + ("Test Underflow ", &test_underflow_message) + ("Test Time Error", &test_time_error_message) + ("Test Late Command", &test_late_command_message) + ("Test Broken Chain", &test_broken_chain_message) + ; + + //init result counts + uhd::dict<std::string, size_t> failures, successes; + BOOST_FOREACH(const std::string &key, tests.keys()){ + failures[key] = 0; + successes[key] = 0; + } + + //run the tests, pick at random + std::srand((unsigned int) time(NULL)); + for (size_t n = 0; n < ntests; n++){ + std::string key = tests.keys()[std::rand() % tests.size()]; + bool pass = tests[key](usrp, rx_stream, tx_stream); + flush_async(usrp); + flush_recv(rx_stream); + + //store result + if (pass) successes[key]++; + else failures[key]++; + } + + //print the result summary + std::cout << std::endl << "Summary:" << std::endl << std::endl; + BOOST_FOREACH(const std::string &key, tests.keys()){ + std::cout << boost::format( + "%s -> %3u successes, %3u failures" + ) % key % successes[key] % failures[key] << std::endl; + } + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/test_pps_input.cpp b/host/examples/test_pps_input.cpp new file mode 100644 index 000000000..994b7ca87 --- /dev/null +++ b/host/examples/test_pps_input.cpp @@ -0,0 +1,62 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD Test PPS Input %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the time at an unknown pps (will throw if no pps) + std::cout << std::endl << "Attempt to detect the PPS and set the time..." << std::endl << std::endl; + usrp->set_time_unknown_pps(uhd::time_spec_t(0.0)); + std::cout << std::endl << "Success!" << std::endl << std::endl; + return 0; +} diff --git a/host/examples/tx_bursts.cpp b/host/examples/tx_bursts.cpp new file mode 100644 index 000000000..f5ae18a9f --- /dev/null +++ b/host/examples/tx_bursts.cpp @@ -0,0 +1,164 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <csignal> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +static bool stop_signal_called = false; +void sig_int_handler(int){stop_signal_called = true;} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + double seconds_in_future; + size_t total_num_samps; + double rate; + float ampl; + double freq; + double rep_rate; + double gain; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args") + ("secs", po::value<double>(&seconds_in_future)->default_value(1.5), "delay before first burst") + ("repeat", "repeat burst") + ("rep-delay", po::value<double>(&rep_rate)->default_value(0.5), "delay between bursts") + ("nsamps", po::value<size_t>(&total_num_samps)->default_value(10000), "total number of samples to transmit") + ("rate", po::value<double>(&rate)->default_value(100e6/16), "rate of outgoing samples") + ("ampl", po::value<float>(&l)->default_value(float(0.3)), "amplitude of each sample") + ("freq", po::value<double>(&freq)->default_value(0), "center frequency") + ("gain", po::value<double>(&gain)->default_value(0), "gain") + ("dilv", "specify to disable inner-loop verbose") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD TX Timed Samples %s") % desc << std::endl; + return ~0; + } + + bool verbose = vm.count("dilv") == 0; + bool repeat = vm.count("repeat") != 0; + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the tx sample rate + std::cout << boost::format("Setting TX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_tx_rate(rate); + std::cout << boost::format("Actual TX Rate: %f Msps...") % (usrp->get_tx_rate()/1e6) << std::endl << std::endl; + + std::cout << boost::format("Setting TX Freq: %f MHz...") % (freq/1e6) << std::endl; + for(size_t i=0; i < usrp->get_tx_num_channels(); i++) usrp->set_tx_freq(freq, i); + std::cout << boost::format("Actual TX Freq: %f MHz...") % (usrp->get_tx_freq()/1e6) << std::endl << std::endl; + + std::cout << boost::format("Setting TX Gain: %f...") % (gain) << std::endl; + for(size_t i=0; i < usrp->get_tx_num_channels(); i++) usrp->set_tx_gain(gain, i); + std::cout << boost::format("Actual TX Gain: %f...") % (usrp->get_tx_gain()) << std::endl << std::endl; + + std::cout << boost::format("Setting device timestamp to 0...") << std::endl; + usrp->set_time_now(uhd::time_spec_t(0.0)); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //allocate buffer with data to send + const size_t spb = tx_stream->get_max_num_samps(); + + std::vector<std::complex<float> *> buffs; + for(size_t i=0; i < usrp->get_num_mboards(); i++) { + buffs.push_back(new std::complex<float>[spb]); + for(size_t n=0; n < spb; n++) + buffs.back()[n] = std::complex<float>(ampl, ampl); + }; + + std::signal(SIGINT, &sig_int_handler); + if(repeat) std::cout << "Press Ctrl + C to quit..." << std::endl; + + double time_to_send = seconds_in_future; + + do { + //setup metadata for the first packet + uhd::tx_metadata_t md; + md.start_of_burst = true; + md.end_of_burst = false; + md.has_time_spec = true; + md.time_spec = uhd::time_spec_t(time_to_send); + + //the first call to send() will block this many seconds before sending: + double timeout = std::max(rep_rate, seconds_in_future) + 0.1; //timeout (delay before transmit + padding) + + size_t num_acc_samps = 0; //number of accumulated samples + while(num_acc_samps < total_num_samps){ + size_t samps_to_send = std::min(total_num_samps - num_acc_samps, spb); + + //ensure the the last packet has EOB set + md.end_of_burst = samps_to_send < spb; + + //send a single packet + size_t num_tx_samps = tx_stream->send( + buffs, samps_to_send, md, timeout + ); + + //do not use time spec for subsequent packets + md.has_time_spec = false; + md.start_of_burst = false; + + if (num_tx_samps < samps_to_send) std::cerr << "Send timeout..." << std::endl; + if(verbose) std::cout << boost::format("Sent packet: %u samples") % num_tx_samps << std::endl; + + num_acc_samps += num_tx_samps; + } + + time_to_send += rep_rate; + + std::cout << std::endl << "Waiting for async burst ACK... " << std::flush; + uhd::async_metadata_t async_md; + bool got_async_burst_ack = false; + //loop through all messages for the ACK packet (may have underflow messages in queue) + while (not got_async_burst_ack and usrp->get_device()->recv_async_msg(async_md, seconds_in_future)){ + got_async_burst_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK); + } + std::cout << (got_async_burst_ack? "success" : "fail") << std::endl; + } while (not stop_signal_called and repeat); + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/tx_samples_from_file.cpp b/host/examples/tx_samples_from_file.cpp new file mode 100644 index 000000000..ea4add403 --- /dev/null +++ b/host/examples/tx_samples_from_file.cpp @@ -0,0 +1,174 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <iostream> +#include <fstream> +#include <complex> + +namespace po = boost::program_options; + +template<typename samp_type> void send_from_file( + uhd::usrp::multi_usrp::sptr usrp, + const std::string &cpu_format, + const std::string &file, + size_t samps_per_buff +){ + //create a transmit streamer + uhd::stream_args_t stream_args(cpu_format); + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + uhd::tx_metadata_t md; + md.start_of_burst = false; + md.end_of_burst = false; + std::vector<samp_type> buff(samps_per_buff); + std::ifstream infile(file.c_str(), std::ifstream::binary); + + //loop until the entire file has been read + while(not md.end_of_burst){ + + infile.read((char*)&buff.front(), buff.size()*sizeof(samp_type)); + size_t num_tx_samps = infile.gcount()/sizeof(samp_type); + + md.end_of_burst = infile.eof(); + + tx_stream->send(&buff.front(), num_tx_samps, md); + } + + infile.close(); +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args, file, type, ant, subdev, ref; + size_t spb; + double rate, freq, gain, bw; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args") + ("file", po::value<std::string>(&file)->default_value("usrp_samples.dat"), "name of the file to read binary samples from") + ("type", po::value<std::string>(&type)->default_value("short"), "sample type: double, float, or short") + ("spb", po::value<size_t>(&spb)->default_value(10000), "samples per buffer") + ("rate", po::value<double>(&rate), "rate of outgoing samples") + ("freq", po::value<double>(&freq), "RF center frequency in Hz") + ("gain", po::value<double>(&gain), "gain for the RF chain") + ("ant", po::value<std::string>(&ant), "daughterboard antenna selection") + ("subdev", po::value<std::string>(&subdev), "daughterboard subdevice specification") + ("bw", po::value<double>(&bw), "daughterboard IF filter bandwidth in Hz") + ("ref", po::value<std::string>(&ref)->default_value("internal"), "waveform type (internal, external, mimo)") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD TX samples from file %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //Lock mboard clocks + usrp->set_clock_source(ref); + + //always select the subdevice first, the channel mapping affects the other settings + if (vm.count("subdev")) usrp->set_tx_subdev_spec(subdev); + + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the sample rate + if (not vm.count("rate")){ + std::cerr << "Please specify the sample rate with --rate" << std::endl; + return ~0; + } + std::cout << boost::format("Setting TX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_tx_rate(rate); + std::cout << boost::format("Actual TX Rate: %f Msps...") % (usrp->get_tx_rate()/1e6) << std::endl << std::endl; + + //set the center frequency + if (not vm.count("freq")){ + std::cerr << "Please specify the center frequency with --freq" << std::endl; + return ~0; + } + std::cout << boost::format("Setting TX Freq: %f MHz...") % (freq/1e6) << std::endl; + usrp->set_tx_freq(freq); + std::cout << boost::format("Actual TX Freq: %f MHz...") % (usrp->get_tx_freq()/1e6) << std::endl << std::endl; + + //set the rf gain + if (vm.count("gain")){ + std::cout << boost::format("Setting TX Gain: %f dB...") % gain << std::endl; + usrp->set_tx_gain(gain); + std::cout << boost::format("Actual TX Gain: %f dB...") % usrp->get_tx_gain() << std::endl << std::endl; + } + + //set the IF filter bandwidth + if (vm.count("bw")){ + std::cout << boost::format("Setting TX Bandwidth: %f MHz...") % bw << std::endl; + usrp->set_tx_bandwidth(bw); + std::cout << boost::format("Actual TX Bandwidth: %f MHz...") % usrp->get_tx_bandwidth() << std::endl << std::endl; + } + + //set the antenna + if (vm.count("ant")) usrp->set_tx_antenna(ant); + + boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for some setup time + + //Check Ref and LO Lock detect + std::vector<std::string> sensor_names; + sensor_names = usrp->get_tx_sensor_names(0); + if (std::find(sensor_names.begin(), sensor_names.end(), "lo_locked") != sensor_names.end()) { + uhd::sensor_value_t lo_locked = usrp->get_tx_sensor("lo_locked",0); + std::cout << boost::format("Checking TX: %s ...") % lo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(lo_locked.to_bool()); + } + sensor_names = usrp->get_mboard_sensor_names(0); + if ((ref == "mimo") and (std::find(sensor_names.begin(), sensor_names.end(), "mimo_locked") != sensor_names.end())) { + uhd::sensor_value_t mimo_locked = usrp->get_mboard_sensor("mimo_locked",0); + std::cout << boost::format("Checking TX: %s ...") % mimo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(mimo_locked.to_bool()); + } + if ((ref == "external") and (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end())) { + uhd::sensor_value_t ref_locked = usrp->get_mboard_sensor("ref_locked",0); + std::cout << boost::format("Checking TX: %s ...") % ref_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(ref_locked.to_bool()); + } + + //send from file + if (type == "double") send_from_file<std::complex<double> >(usrp, "fc64", file, spb); + else if (type == "float") send_from_file<std::complex<float> >(usrp, "fc32", file, spb); + else if (type == "short") send_from_file<std::complex<short> >(usrp, "sc16", file, spb); + else throw std::runtime_error("Unknown type " + type); + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/tx_timed_samples.cpp b/host/examples/tx_timed_samples.cpp new file mode 100644 index 000000000..3b8cc75d4 --- /dev/null +++ b/host/examples/tx_timed_samples.cpp @@ -0,0 +1,128 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + double seconds_in_future; + size_t total_num_samps; + double rate; + float ampl; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ("secs", po::value<double>(&seconds_in_future)->default_value(1.5), "number of seconds in the future to transmit") + ("nsamps", po::value<size_t>(&total_num_samps)->default_value(10000), "total number of samples to transmit") + ("rate", po::value<double>(&rate)->default_value(100e6/16), "rate of outgoing samples") + ("ampl", po::value<float>(&l)->default_value(float(0.3)), "amplitude of each sample") + ("dilv", "specify to disable inner-loop verbose") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD TX Timed Samples %s") % desc << std::endl; + return ~0; + } + + bool verbose = vm.count("dilv") == 0; + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the tx sample rate + std::cout << boost::format("Setting TX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_tx_rate(rate); + std::cout << boost::format("Actual TX Rate: %f Msps...") % (usrp->get_tx_rate()/1e6) << std::endl << std::endl; + + std::cout << boost::format("Setting device timestamp to 0...") << std::endl; + usrp->set_time_now(uhd::time_spec_t(0.0)); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //allocate buffer with data to send + std::vector<std::complex<float> > buff(tx_stream->get_max_num_samps(), std::complex<float>(ampl, ampl)); + + //setup metadata for the first packet + uhd::tx_metadata_t md; + md.start_of_burst = false; + md.end_of_burst = false; + md.has_time_spec = true; + md.time_spec = uhd::time_spec_t(seconds_in_future); + + //the first call to send() will block this many seconds before sending: + const double timeout = seconds_in_future + 0.1; //timeout (delay before transmit + padding) + + size_t num_acc_samps = 0; //number of accumulated samples + while(num_acc_samps < total_num_samps){ + size_t samps_to_send = std::min(total_num_samps - num_acc_samps, buff.size()); + + //send a single packet + size_t num_tx_samps = tx_stream->send( + &buff.front(), samps_to_send, md, timeout + ); + + //do not use time spec for subsequent packets + md.has_time_spec = false; + + if (num_tx_samps < samps_to_send) std::cerr << "Send timeout..." << std::endl; + if(verbose) std::cout << boost::format("Sent packet: %u samples") % num_tx_samps << std::endl; + + num_acc_samps += num_tx_samps; + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); + + std::cout << std::endl << "Waiting for async burst ACK... " << std::flush; + uhd::async_metadata_t async_md; + bool got_async_burst_ack = false; + //loop through all messages for the ACK packet (may have underflow messages in queue) + while (not got_async_burst_ack and usrp->get_device()->recv_async_msg(async_md, timeout)){ + got_async_burst_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK); + } + std::cout << (got_async_burst_ack? "success" : "fail") << std::endl; + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} diff --git a/host/examples/tx_waveforms.cpp b/host/examples/tx_waveforms.cpp new file mode 100644 index 000000000..6a377fdac --- /dev/null +++ b/host/examples/tx_waveforms.cpp @@ -0,0 +1,261 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/exception.hpp> +#include <boost/program_options.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <complex> +#include <csignal> +#include <cmath> + +namespace po = boost::program_options; + +/*********************************************************************** + * Signal handlers + **********************************************************************/ +static bool stop_signal_called = false; +void sig_int_handler(int){stop_signal_called = true;} + +/*********************************************************************** + * Waveform generators + **********************************************************************/ +static const size_t wave_table_len = 8192; + +class wave_table_class{ +public: + wave_table_class(const std::string &wave_type, const float ampl): + _wave_table(wave_table_len) + { + //compute real wave table with 1.0 amplitude + std::vector<double> real_wave_table(wave_table_len); + if (wave_type == "CONST"){ + for (size_t i = 0; i < wave_table_len; i++) + real_wave_table[i] = 1.0; + } + else if (wave_type == "SQUARE"){ + for (size_t i = 0; i < wave_table_len; i++) + real_wave_table[i] = (i < wave_table_len/2)? 0.0 : 1.0; + } + else if (wave_type == "RAMP"){ + for (size_t i = 0; i < wave_table_len; i++) + real_wave_table[i] = 2.0*i/(wave_table_len-1) - 1.0; + } + else if (wave_type == "SINE"){ + static const double tau = 2*std::acos(-1.0); + for (size_t i = 0; i < wave_table_len; i++) + real_wave_table[i] = std::sin((tau*i)/wave_table_len); + } + else throw std::runtime_error("unknown waveform type: " + wave_type); + + //compute i and q pairs with 90% offset and scale to amplitude + for (size_t i = 0; i < wave_table_len; i++){ + const size_t q = (i+(3*wave_table_len)/4)%wave_table_len; + _wave_table[i] = std::complex<float>(ampl*real_wave_table[i], ampl*real_wave_table[q]); + } + } + + inline std::complex<float> operator()(const size_t index) const{ + return _wave_table[index % wave_table_len]; + } + +private: + std::vector<std::complex<float> > _wave_table; +}; + +/*********************************************************************** + * Main function + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args, wave_type, ant, subdev, ref, otw; + size_t spb; + double rate, freq, gain, wave_freq, bw; + float ampl; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ("spb", po::value<size_t>(&spb)->default_value(0), "samples per buffer, 0 for default") + ("rate", po::value<double>(&rate), "rate of outgoing samples") + ("freq", po::value<double>(&freq), "RF center frequency in Hz") + ("ampl", po::value<float>(&l)->default_value(float(0.3)), "amplitude of the waveform [0 to 0.7]") + ("gain", po::value<double>(&gain), "gain for the RF chain") + ("ant", po::value<std::string>(&ant), "daughterboard antenna selection") + ("subdev", po::value<std::string>(&subdev), "daughterboard subdevice specification") + ("bw", po::value<double>(&bw), "daughterboard IF filter bandwidth in Hz") + ("wave-type", po::value<std::string>(&wave_type)->default_value("CONST"), "waveform type (CONST, SQUARE, RAMP, SINE)") + ("wave-freq", po::value<double>(&wave_freq)->default_value(0), "waveform frequency in Hz") + ("ref", po::value<std::string>(&ref)->default_value("internal"), "clock reference (internal, external, mimo)") + ("otw", po::value<std::string>(&otw)->default_value("sc16"), "specify the over-the-wire sample mode") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD TX Waveforms %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //Lock mboard clocks + usrp->set_clock_source(ref); + + //always select the subdevice first, the channel mapping affects the other settings + if (vm.count("subdev")) usrp->set_tx_subdev_spec(subdev); + + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //set the sample rate + if (not vm.count("rate")){ + std::cerr << "Please specify the sample rate with --rate" << std::endl; + return ~0; + } + std::cout << boost::format("Setting TX Rate: %f Msps...") % (rate/1e6) << std::endl; + usrp->set_tx_rate(rate); + std::cout << boost::format("Actual TX Rate: %f Msps...") % (usrp->get_tx_rate()/1e6) << std::endl << std::endl; + + //set the center frequency + if (not vm.count("freq")){ + std::cerr << "Please specify the center frequency with --freq" << std::endl; + return ~0; + } + + for(size_t chan = 0; chan < usrp->get_tx_num_channels(); chan++) { + std::cout << boost::format("Setting TX Freq: %f MHz...") % (freq/1e6) << std::endl; + usrp->set_tx_freq(freq, chan); + std::cout << boost::format("Actual TX Freq: %f MHz...") % (usrp->get_tx_freq(chan)/1e6) << std::endl << std::endl; + + //set the rf gain + if (vm.count("gain")){ + std::cout << boost::format("Setting TX Gain: %f dB...") % gain << std::endl; + usrp->set_tx_gain(gain, chan); + std::cout << boost::format("Actual TX Gain: %f dB...") % usrp->get_tx_gain(chan) << std::endl << std::endl; + } + + //set the IF filter bandwidth + if (vm.count("bw")){ + std::cout << boost::format("Setting TX Bandwidth: %f MHz...") % bw << std::endl; + usrp->set_tx_bandwidth(bw, chan); + std::cout << boost::format("Actual TX Bandwidth: %f MHz...") % usrp->get_tx_bandwidth(chan) << std::endl << std::endl; + } + + //set the antenna + if (vm.count("ant")) usrp->set_tx_antenna(ant, chan); + } + + //for the const wave, set the wave freq for small samples per period + if (wave_freq == 0 and wave_type == "CONST"){ + wave_freq = usrp->get_tx_rate()/2; + } + + //error when the waveform is not possible to generate + if (std::abs(wave_freq) > usrp->get_tx_rate()/2){ + throw std::runtime_error("wave freq out of Nyquist zone"); + } + if (usrp->get_tx_rate()/std::abs(wave_freq) > wave_table_len/2){ + throw std::runtime_error("wave freq too small for table"); + } + + //pre-compute the waveform values + const wave_table_class wave_table(wave_type, ampl); + const size_t step = boost::math::iround(wave_freq/usrp->get_tx_rate() * wave_table_len); + size_t index = 0; + + //create a transmit streamer + //linearly map channels (index0 = channel0, index1 = channel1, ...) + uhd::stream_args_t stream_args("fc32", otw); + for (size_t chan = 0; chan < usrp->get_tx_num_channels(); chan++) + stream_args.channels.push_back(chan); //linear mapping + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //allocate a buffer which we re-use for each channel + if (spb == 0) spb = tx_stream->get_max_num_samps()*10; + std::vector<std::complex<float> > buff(spb); + std::vector<std::complex<float> *> buffs(usrp->get_tx_num_channels(), &buff.front()); + + //setup the metadata flags + uhd::tx_metadata_t md; + md.start_of_burst = true; + md.end_of_burst = false; + md.has_time_spec = true; + md.time_spec = uhd::time_spec_t(0.1); + + std::cout << boost::format("Setting device timestamp to 0...") << std::endl; + usrp->set_time_now(uhd::time_spec_t(0.0)); + + //Check Ref and LO Lock detect + std::vector<std::string> sensor_names; + sensor_names = usrp->get_tx_sensor_names(0); + if (std::find(sensor_names.begin(), sensor_names.end(), "lo_locked") != sensor_names.end()) { + uhd::sensor_value_t lo_locked = usrp->get_tx_sensor("lo_locked",0); + std::cout << boost::format("Checking TX: %s ...") % lo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(lo_locked.to_bool()); + } + sensor_names = usrp->get_mboard_sensor_names(0); + if ((ref == "mimo") and (std::find(sensor_names.begin(), sensor_names.end(), "mimo_locked") != sensor_names.end())) { + uhd::sensor_value_t mimo_locked = usrp->get_mboard_sensor("mimo_locked",0); + std::cout << boost::format("Checking TX: %s ...") % mimo_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(mimo_locked.to_bool()); + } + if ((ref == "external") and (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end())) { + uhd::sensor_value_t ref_locked = usrp->get_mboard_sensor("ref_locked",0); + std::cout << boost::format("Checking TX: %s ...") % ref_locked.to_pp_string() << std::endl; + UHD_ASSERT_THROW(ref_locked.to_bool()); + } + + std::signal(SIGINT, &sig_int_handler); + std::cout << "Press Ctrl + C to stop streaming..." << std::endl; + + //send data until the signal handler gets called + while(not stop_signal_called){ + //fill the buffer with the waveform + for (size_t n = 0; n < buff.size(); n++){ + buff[n] = wave_table(index += step); + } + + //send the entire contents of the buffer + tx_stream->send(buffs, buff.size(), md); + + md.start_of_burst = false; + md.has_time_spec = false; + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + return 0; +} diff --git a/host/include/CMakeLists.txt b/host/include/CMakeLists.txt new file mode 100644 index 000000000..3f7ca2cb7 --- /dev/null +++ b/host/include/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + + +ADD_SUBDIRECTORY(uhd) diff --git a/host/include/uhd/CMakeLists.txt b/host/include/uhd/CMakeLists.txt new file mode 100644 index 000000000..1df04d577 --- /dev/null +++ b/host/include/uhd/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + + +ADD_SUBDIRECTORY(transport) +ADD_SUBDIRECTORY(types) +ADD_SUBDIRECTORY(usrp) +ADD_SUBDIRECTORY(utils) + +INSTALL(FILES + config.hpp + convert.hpp + deprecated.hpp + device.hpp + device_deprecated.ipp + exception.hpp + property_tree.ipp + property_tree.hpp + stream.hpp + version.hpp + DESTINATION ${INCLUDE_DIR}/uhd + COMPONENT headers +) diff --git a/host/include/uhd/config.hpp b/host/include/uhd/config.hpp new file mode 100644 index 000000000..6fd2932cf --- /dev/null +++ b/host/include/uhd/config.hpp @@ -0,0 +1,92 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_CONFIG_HPP +#define INCLUDED_UHD_CONFIG_HPP + +#include <boost/config.hpp> + +#ifdef BOOST_MSVC +// suppress warnings +//# pragma warning(push) +//# pragma warning(disable: 4511) // copy constructor can't not be generated +//# pragma warning(disable: 4512) // assignment operator can't not be generated +//# pragma warning(disable: 4100) // unreferenced formal parameter +//# pragma warning(disable: 4996) // <symbol> was declared deprecated +# pragma warning(disable: 4355) // 'this' : used in base member initializer list +//# pragma warning(disable: 4706) // assignment within conditional expression +# pragma warning(disable: 4251) // class 'A<T>' needs to have dll-interface to be used by clients of class 'B' +//# pragma warning(disable: 4127) // conditional expression is constant +//# pragma warning(disable: 4290) // C++ exception specification ignored except to ... +//# pragma warning(disable: 4180) // qualifier applied to function type has no meaning; ignored +# pragma warning(disable: 4275) // non dll-interface class ... used as base for dll-interface class ... +//# pragma warning(disable: 4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data +//# pragma warning(disable: 4511) // 'class' : copy constructor could not be generated +//# pragma warning(disable: 4250) // 'class' : inherits 'method' via dominance +# pragma warning(disable: 4200) // nonstandard extension used : zero-sized array in struct/union + +// define logical operators +#include <ciso646> + +// define ssize_t +#include <cstddef> +typedef ptrdiff_t ssize_t; + +#endif //BOOST_MSVC + +//define cross platform attribute macros +#if defined(BOOST_MSVC) + #define UHD_EXPORT __declspec(dllexport) + #define UHD_IMPORT __declspec(dllimport) + #define UHD_INLINE __forceinline + #define UHD_DEPRECATED __declspec(deprecated) + #define UHD_ALIGNED(x) __declspec(align(x)) +#elif defined(__GNUG__) && __GNUG__ >= 4 + #define UHD_EXPORT __attribute__((visibility("default"))) + #define UHD_IMPORT __attribute__((visibility("default"))) + #define UHD_INLINE inline __attribute__((always_inline)) + #define UHD_DEPRECATED __attribute__((deprecated)) + #define UHD_ALIGNED(x) __attribute__((aligned(x))) +#else + #define UHD_EXPORT + #define UHD_IMPORT + #define UHD_INLINE inline + #define UHD_DEPRECATED + #define UHD_ALIGNED(x) +#endif + +// Define API declaration macro +#ifdef UHD_DLL_EXPORTS + #define UHD_API UHD_EXPORT +#else + #define UHD_API UHD_IMPORT +#endif // UHD_DLL_EXPORTS + +// Platform defines for conditional parts of headers: +// Taken from boost/config/select_platform_config.hpp, +// however, we define macros, not strings for platforms. +#if defined(linux) || defined(__linux) || defined(__linux__) + #define UHD_PLATFORM_LINUX +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) + #define UHD_PLATFORM_WIN32 +#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) + #define UHD_PLATFORM_MACOS +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define UHD_PLATFORM_BSD +#endif + +#endif /* INCLUDED_UHD_CONFIG_HPP */ diff --git a/host/include/uhd/convert.hpp b/host/include/uhd/convert.hpp new file mode 100644 index 000000000..f906ff0e9 --- /dev/null +++ b/host/include/uhd/convert.hpp @@ -0,0 +1,101 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_CONVERT_HPP +#define INCLUDED_UHD_CONVERT_HPP + +#include <uhd/config.hpp> +#include <uhd/types/ref_vector.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <boost/operators.hpp> +#include <string> + +namespace uhd{ namespace convert{ + + //! A conversion class that implements a conversion from inputs -> outputs. + class converter{ + public: + typedef boost::shared_ptr<converter> sptr; + typedef uhd::ref_vector<void *> output_type; + typedef uhd::ref_vector<const void *> input_type; + + //! Set the scale factor (used in floating point conversions) + virtual void set_scalar(const double) = 0; + + //! The public conversion method to convert inputs -> outputs + UHD_INLINE void conv(const input_type &in, const output_type &out, const size_t num){ + if (num != 0) (*this)(in, out, num); + } + + private: + //! Callable method: input vectors, output vectors, num samples + virtual void operator()(const input_type&, const output_type&, const size_t) = 0; + }; + + //! Conversion factory function typedef + typedef boost::function<converter::sptr(void)> function_type; + + //! Priority of conversion routines + typedef int priority_type; + + //! Identify a conversion routine in the registry + struct id_type : boost::equality_comparable<id_type>{ + std::string input_format; + size_t num_inputs; + std::string output_format; + size_t num_outputs; + std::string to_pp_string(void) const; + }; + + //! Implement equality_comparable interface + UHD_API bool operator==(const id_type &, const id_type &); + + /*! + * Register a converter function. + * \param id identify the conversion + * \param fcn makes a new converter + * \param prio the function priority + */ + UHD_API void register_converter( + const id_type &id, + const function_type &fcn, + const priority_type prio + ); + + /*! + * Get a converter factory function. + * \param id identify the conversion + * \return the converter factory function + */ + UHD_API function_type get_converter(const id_type &id); + + /*! + * Register the size of a particular item. + * \param format the item format + * \param size the size in bytes + */ + UHD_API void register_bytes_per_item( + const std::string &format, const size_t size + ); + + //! Convert an item format to a size in bytes + UHD_API size_t get_bytes_per_item(const std::string &format); + +}} //namespace + +#endif /* INCLUDED_UHD_CONVERT_HPP */ diff --git a/host/include/uhd/deprecated.hpp b/host/include/uhd/deprecated.hpp new file mode 100644 index 000000000..95cce58e9 --- /dev/null +++ b/host/include/uhd/deprecated.hpp @@ -0,0 +1,76 @@ +//---------------------------------------------------------------------- +//-- deprecated interfaces below, to be removed when the API is changed +//---------------------------------------------------------------------- + +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_OTW_TYPE_HPP +#define INCLUDED_UHD_TYPES_OTW_TYPE_HPP + +#include <uhd/config.hpp> + +namespace uhd{ + + /*! + * Description for over-the-wire integers: + * The DSP units in the FPGA deal with signed 16-bit integers. + * The width and shift define the translation between OTW and DSP, + * defined by the following relation: otw_int = dsp_int >> shift + * + * Note: possible combinations of width, shift, and byteorder + * depend on the internals of the FPGA. Not all are supported! + */ + struct UHD_API otw_type_t{ + + /*! + * Width of an over-the-wire integer in bits. + */ + size_t width; //in bits + + /*! + * Shift of an over-the-wire integer in bits. + * otw_int = dsp_int >> shift + * dsp_int = otw_int << shift + */ + size_t shift; //in bits + + /*! + * Constants for byte order (borrowed from numpy's dtype) + */ + enum /*bo_t*/ { + BO_NATIVE = int('='), + BO_LITTLE_ENDIAN = int('<'), + BO_BIG_ENDIAN = int('>'), + BO_NOT_APPLICABLE = int('|') + } byteorder; + + /*! + * Get the sample size of this otw type. + * \return the size of a sample in bytes + */ + size_t get_sample_size(void) const; + + otw_type_t(void); + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_OTW_TYPE_HPP */ + +#include <uhd/types/io_type.hpp> //wish it was in here +#include <uhd/types/clock_config.hpp> //wish it was in here diff --git a/host/include/uhd/device.hpp b/host/include/uhd/device.hpp new file mode 100644 index 000000000..89c6332da --- /dev/null +++ b/host/include/uhd/device.hpp @@ -0,0 +1,104 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_DEVICE_HPP +#define INCLUDED_UHD_DEVICE_HPP + +#include <uhd/config.hpp> +#include <uhd/stream.hpp> +#include <uhd/deprecated.hpp> +#include <uhd/types/device_addr.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> + +namespace uhd{ + +class property_tree; //forward declaration + +/*! + * The usrp device interface represents the usrp hardware. + * The api allows for discovery, configuration, and streaming. + */ +class UHD_API device : boost::noncopyable{ + +public: + typedef boost::shared_ptr<device> sptr; + typedef boost::function<device_addrs_t(const device_addr_t &)> find_t; + typedef boost::function<sptr(const device_addr_t &)> make_t; + + /*! + * Register a device into the discovery and factory system. + * + * \param find a function that discovers devices + * \param make a factory function that makes a device + */ + static void register_device( + const find_t &find, + const make_t &make + ); + + /*! + * \brief Find usrp devices attached to the host. + * + * The hint device address should be used to narrow down the search + * to particular transport types and/or transport arguments. + * + * \param hint a partially (or fully) filled in device address + * \return a vector of device addresses for all usrps on the system + */ + static device_addrs_t find(const device_addr_t &hint); + + /*! + * \brief Create a new usrp device from the device address hint. + * + * The make routine will call find and pick one of the results. + * By default, the first result will be used to create a new device. + * Use the which parameter as an index into the list of results. + * + * \param hint a partially (or fully) filled in device address + * \param which which address to use when multiple are found + * \return a shared pointer to a new device instance + */ + static sptr make(const device_addr_t &hint, size_t which = 0); + + //! Make a new receive streamer from the streamer arguments + virtual rx_streamer::sptr get_rx_stream(const stream_args_t &args) = 0; + + //! Make a new transmit streamer from the streamer arguments + virtual tx_streamer::sptr get_tx_stream(const stream_args_t &args) = 0; + + /*! + * Receive and asynchronous message from the device. + * \param async_metadata the metadata to be filled in + * \param timeout the timeout in seconds to wait for a message + * \return true when the async_metadata is valid, false for timeout + */ + virtual bool recv_async_msg( + async_metadata_t &async_metadata, double timeout = 0.1 + ) = 0; + + //! Get access to the underlying property structure + virtual boost::shared_ptr<property_tree> get_tree(void) const = 0; + + #include <uhd/device_deprecated.ipp> + +}; + +} //namespace uhd + +#endif /* INCLUDED_UHD_DEVICE_HPP */ diff --git a/host/include/uhd/device_deprecated.ipp b/host/include/uhd/device_deprecated.ipp new file mode 100644 index 000000000..0ee1cd706 --- /dev/null +++ b/host/include/uhd/device_deprecated.ipp @@ -0,0 +1,191 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +//this file is included inside device class +//it supports the old send/recv functions +//this was replaced by the streamer API + +#define _lazymin(x, y) (((x) > (y))? (y) : (x)) + +/*! + * Send modes for the device send routine. + */ +enum send_mode_t{ + //! Tells the send routine to send the entire buffer + SEND_MODE_FULL_BUFF = 0, + //! Tells the send routine to return after one packet + SEND_MODE_ONE_PACKET = 1 +}; + +/*! + * Recv modes for the device recv routine. + */ +enum recv_mode_t{ + //! Tells the recv routine to recv the entire buffer + RECV_MODE_FULL_BUFF = 0, + //! Tells the recv routine to return after one packet + RECV_MODE_ONE_PACKET = 1 +}; + +//! Typedef for a pointer to a single, or a collection of send buffers +typedef ref_vector<const void *> send_buffs_type; + +//! Typedef for a pointer to a single, or a collection of recv buffers +typedef ref_vector<void *> recv_buffs_type; + +/*! + * Send buffers containing IF data described by the metadata. + * + * Send handles fragmentation as follows: + * If the buffer has more samples than the maximum per packet, + * the send method will fragment the samples across several packets. + * Send will respect the burst flags when fragmenting to ensure + * that start of burst can only be set on the first fragment and + * that end of burst can only be set on the final fragment. + * Fragmentation only applies in the full buffer send mode. + * + * This is a blocking call and will not return until the number + * of samples returned have been read out of each buffer. + * Under a timeout condition, the number of samples returned + * may be less than the number of samples specified. + * + * \param buffs a vector of read-only memory containing IF data + * \param nsamps_per_buff the number of samples to send, per buffer + * \param metadata data describing the buffer's contents + * \param io_type the type of data loaded in the buffer + * \param send_mode tells send how to unload the buffer + * \param timeout the timeout in seconds to wait on a packet + * \return the number of samples sent + */ +size_t send( + const send_buffs_type &buffs, + size_t nsamps_per_buff, + const tx_metadata_t &metadata, + const io_type_t &io_type, + send_mode_t send_mode, + double timeout = 0.1 +){ + if (_tx_streamer.get() == NULL or _tx_streamer->get_num_channels() != buffs.size() or _send_tid != io_type.tid){ + _send_tid = io_type.tid; + _tx_streamer.reset(); //cleanup possible old one + stream_args_t args; + args.cpu_format = (_send_tid == io_type_t::COMPLEX_FLOAT32)? "fc32" : "sc16"; + args.otw_format = "sc16"; + args.args["noclear"] = "1"; + for (size_t ch = 0; ch < buffs.size(); ch++) + args.channels.push_back(ch); //linear mapping + _tx_streamer = get_tx_stream(args); + } + const size_t nsamps = (send_mode == SEND_MODE_ONE_PACKET)? + _lazymin(nsamps_per_buff, get_max_send_samps_per_packet()) : + nsamps_per_buff; + return _tx_streamer->send(buffs, nsamps, metadata, timeout); +} + +/*! + * Receive buffers containing IF data described by the metadata. + * + * Receive handles fragmentation as follows: + * If the buffer has insufficient space to hold all samples + * that were received in a single packet over-the-wire, + * then the buffer will be completely filled and the implementation + * will hold a pointer into the remaining portion of the packet. + * Subsequent calls will load from the remainder of the packet, + * and will flag the metadata to show that this is a fragment. + * The next call to receive, after the remainder becomes exahausted, + * will perform an over-the-wire receive as usual. + * See the rx metadata fragment flags and offset fields for details. + * + * This is a blocking call and will not return until the number + * of samples returned have been written into each buffer. + * Under a timeout condition, the number of samples returned + * may be less than the number of samples specified. + * + * When using the full buffer recv mode, the metadata only applies + * to the first packet received and written into the recv buffers. + * Use the one packet recv mode to get per packet metadata. + * + * \param buffs a vector of writable memory to fill with IF data + * \param nsamps_per_buff the size of each buffer in number of samples + * \param metadata data to fill describing the buffer + * \param io_type the type of data to fill into the buffer + * \param recv_mode tells recv how to load the buffer + * \param timeout the timeout in seconds to wait for a packet + * \return the number of samples received or 0 on error + */ +size_t recv( + const recv_buffs_type &buffs, + size_t nsamps_per_buff, + rx_metadata_t &metadata, + const io_type_t &io_type, + recv_mode_t recv_mode, + double timeout = 0.1 +){ + if (_rx_streamer.get() == NULL or _rx_streamer->get_num_channels() != buffs.size() or _recv_tid != io_type.tid){ + _recv_tid = io_type.tid; + _rx_streamer.reset(); //cleanup possible old one + stream_args_t args; + args.cpu_format = (_recv_tid == io_type_t::COMPLEX_FLOAT32)? "fc32" : "sc16"; + args.otw_format = "sc16"; + args.args["noclear"] = "1"; + for (size_t ch = 0; ch < buffs.size(); ch++) + args.channels.push_back(ch); //linear mapping + _rx_streamer = get_rx_stream(args); + } + const size_t nsamps = (recv_mode == RECV_MODE_ONE_PACKET)? + _lazymin(nsamps_per_buff, get_max_recv_samps_per_packet()) : + nsamps_per_buff; + return _rx_streamer->recv(buffs, nsamps, metadata, timeout); +} + +/*! + * Get the maximum number of samples per packet on send. + * \return the number of samples + */ +size_t get_max_send_samps_per_packet(void){ + if (_tx_streamer.get() == NULL){ + stream_args_t args; + args.cpu_format = "fc32"; + args.otw_format = "sc16"; + args.args["noclear"] = "1"; + _tx_streamer = get_tx_stream(args); + _send_tid = io_type_t::COMPLEX_FLOAT32; + } + return _tx_streamer->get_max_num_samps(); +} + +/*! + * Get the maximum number of samples per packet on recv. + * \return the number of samples + */ +size_t get_max_recv_samps_per_packet(void){ + if (_rx_streamer.get() == NULL){ + stream_args_t args; + args.cpu_format = "fc32"; + args.otw_format = "sc16"; + args.args["noclear"] = "1"; + _rx_streamer = get_rx_stream(args); + _recv_tid = io_type_t::COMPLEX_FLOAT32; + } + return _rx_streamer->get_max_num_samps(); +} + +private: + rx_streamer::sptr _rx_streamer; + io_type_t::tid_t _recv_tid; + tx_streamer::sptr _tx_streamer; + io_type_t::tid_t _send_tid; diff --git a/host/include/uhd/exception.hpp b/host/include/uhd/exception.hpp new file mode 100644 index 000000000..c05861788 --- /dev/null +++ b/host/include/uhd/exception.hpp @@ -0,0 +1,166 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_EXCEPTION_HPP +#define INCLUDED_UHD_EXCEPTION_HPP + +#include <uhd/config.hpp> +#include <boost/current_function.hpp> +#include <stdexcept> +#include <string> + +/*! + * Define common exceptions used throughout the code: + * + * - The python built-in exceptions were used as inspiration. + * - Exceptions inherit from std::exception to provide what(). + * - Exceptions inherit from uhd::exception to provide code(). + * + * The code() provides an error code which allows the application + * the option of printing a cryptic error message from the 1990s. + * + * The dynamic_clone() and dynamic_throw() methods allow us to: + * catch an exception by dynamic type (i.e. derived class), save it, + * and later rethrow it, knowing only the static type (i.e. base class), + * and then finally to catch it again using the derived type. + * + * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2106.html + */ +namespace uhd{ + + struct UHD_API exception : std::runtime_error{ + exception(const std::string &what); + virtual unsigned code(void) const = 0; + virtual exception *dynamic_clone(void) const = 0; + virtual void dynamic_throw(void) const = 0; + }; + + struct UHD_API assertion_error : exception{ + assertion_error(const std::string &what); + virtual unsigned code(void) const; + virtual assertion_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API lookup_error : exception{ + lookup_error(const std::string &what); + virtual unsigned code(void) const; + virtual lookup_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API index_error : lookup_error{ + index_error(const std::string &what); + virtual unsigned code(void) const; + virtual index_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API key_error : lookup_error{ + key_error(const std::string &what); + virtual unsigned code(void) const; + virtual key_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API type_error : exception{ + type_error(const std::string &what); + virtual unsigned code(void) const; + virtual type_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API value_error : exception{ + value_error(const std::string &what); + virtual unsigned code(void) const; + virtual value_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API runtime_error : exception{ + runtime_error(const std::string &what); + virtual unsigned code(void) const; + virtual runtime_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API not_implemented_error : runtime_error{ + not_implemented_error(const std::string &what); + virtual unsigned code(void) const; + virtual not_implemented_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API environment_error : exception{ + environment_error(const std::string &what); + virtual unsigned code(void) const; + virtual environment_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API io_error : environment_error{ + io_error(const std::string &what); + virtual unsigned code(void) const; + virtual io_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API os_error : environment_error{ + os_error(const std::string &what); + virtual unsigned code(void) const; + virtual os_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + struct UHD_API system_error : exception{ + system_error(const std::string &what); + virtual unsigned code(void) const; + virtual system_error *dynamic_clone(void) const; + virtual void dynamic_throw(void) const; + }; + + /*! + * Create a formated string with throw-site information. + * Fills in the function name, file name, and line number. + * \param what the std::exeption message + * \return the formatted exception message + */ + #define UHD_THROW_SITE_INFO(what) std::string( \ + std::string(what) + "\n" + \ + " in " + std::string(BOOST_CURRENT_FUNCTION) + "\n" + \ + " at " + std::string(__FILE__) + ":" + BOOST_STRINGIZE(__LINE__) + "\n" \ + ) + + /*! + * Throws an invalid code path exception with throw-site information. + * Use this macro in places that code execution is not supposed to go. + */ + #define UHD_THROW_INVALID_CODE_PATH() \ + throw uhd::system_error(UHD_THROW_SITE_INFO("invalid code path")) + + /*! + * Assert the result of the code evaluation. + * If the code evaluates to false, throw an assertion error. + * \param code the code that resolved to a boolean + */ + #define UHD_ASSERT_THROW(code) if (not (code)) \ + throw uhd::assertion_error(UHD_THROW_SITE_INFO(#code)); \ + else void(0) + +} //namespace uhd + +#endif /* INCLUDED_UHD_EXCEPTION_HPP */ diff --git a/host/include/uhd/property_tree.hpp b/host/include/uhd/property_tree.hpp new file mode 100644 index 000000000..47b2c5736 --- /dev/null +++ b/host/include/uhd/property_tree.hpp @@ -0,0 +1,158 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_PROPERTY_TREE_HPP +#define INCLUDED_UHD_PROPERTY_TREE_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <vector> + +namespace uhd{ + +/*! + * A templated property interface for holding a value + * and registering callbacks when that value changes. + */ +template <typename T> class property : boost::noncopyable{ +public: + typedef boost::function<void(const T &)> subscriber_type; + typedef boost::function<T(void)> publisher_type; + typedef boost::function<T(const T &)> coercer_type; + + /*! + * Register a coercer into the property. + * A coercer is a special subscribes that coerces the value. + * Only one coercer may be registered per property. + * Registering a coercer replaces the previous coercer. + * \param coercer the coercer callback function + * \return a reference to this property for chaining + */ + virtual property<T> &coerce(const coercer_type &coercer) = 0; + + /*! + * Register a publisher into the property. + * A publisher is a special callback the provides the value. + * Publishers are useful for creating read-only properties. + * Only one publisher may be registered per property. + * Registering a publisher replaces the previous publisher. + * \param publisher the publisher callback function + * \return a reference to this property for chaining + */ + virtual property<T> &publish(const publisher_type &publisher) = 0; + + /*! + * Register a subscriber into the property. + * All subscribers are called when the value changes. + * Once a subscriber is registered, it cannot be unregistered. + * \param subscriber the subscriber callback function + * \return a reference to this property for chaining + */ + virtual property<T> &subscribe(const subscriber_type &subscriber) = 0; + + /*! + * Update calls all subscribers w/ the current value. + * \return a reference to this property for chaining + */ + virtual property<T> &update(void) = 0; + + /*! + * Set the new value and call all subscribers. + * The coercer (when provided) is called initially, + * and the coerced value is used to set the subscribers. + * \param value the new value to set on this property + * \return a reference to this property for chaining + */ + virtual property<T> &set(const T &value) = 0; + + /*! + * Get the current value of this property. + * The publisher (when provided) yields the value, + * otherwise an internal shadow is used for the value. + * \return the current value in the property + */ + virtual T get(void) const = 0; + + /*! + * A property is empty if it has never been set. + * A property with a publisher is never empty. + * \return true if the property is empty + */ + virtual bool empty(void) const = 0; +}; + +/*! + * FS Path: A glorified string with path manipulations. + * Inspired by boost filesystem path, but without the dependency. + * + * Notice: we do not declare UHD_API on the whole structure + * because MSVC will do weird things with std::string and linking. + */ +struct fs_path : std::string{ + UHD_API fs_path(void); + UHD_API fs_path(const char *); + UHD_API fs_path(const std::string &); + UHD_API std::string leaf(void) const; + UHD_API fs_path branch_path(void) const; +}; + +UHD_API fs_path operator/(const fs_path &, const fs_path &); + +/*! + * The property tree provides a file system structure for accessing properties. + */ +class UHD_API property_tree : boost::noncopyable{ +public: + typedef boost::shared_ptr<property_tree> sptr; + + //! Create a new + empty property tree + static sptr make(void); + + //! Get a subtree with a new root starting at path + virtual sptr subtree(const fs_path &path) const = 0; + + //! Remove a property or directory (recursive) + virtual void remove(const fs_path &path) = 0; + + //! True if the path exists in the tree + virtual bool exists(const fs_path &path) const = 0; + + //! Get an iterable to all things in the given path + virtual std::vector<std::string> list(const fs_path &path) const = 0; + + //! Create a new property entry in the tree + template <typename T> property<T> &create(const fs_path &path); + + //! Get access to a property in the tree + template <typename T> property<T> &access(const fs_path &path); + +private: + //! Internal create property with wild-card type + virtual void _create(const fs_path &path, const boost::shared_ptr<void> &prop) = 0; + + //! Internal access property with wild-card type + virtual boost::shared_ptr<void> &_access(const fs_path &path) const = 0; + +}; + +} //namespace uhd + +#include <uhd/property_tree.ipp> + +#endif /* INCLUDED_UHD_PROPERTY_TREE_HPP */ diff --git a/host/include/uhd/property_tree.ipp b/host/include/uhd/property_tree.ipp new file mode 100644 index 000000000..85720dc6b --- /dev/null +++ b/host/include/uhd/property_tree.ipp @@ -0,0 +1,95 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_PROPERTY_TREE_IPP +#define INCLUDED_UHD_PROPERTY_TREE_IPP + +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> +#include <vector> + +/*********************************************************************** + * Implement templated property impl + **********************************************************************/ +namespace uhd{ namespace /*anon*/{ + +template <typename T> class property_impl : public property<T>{ +public: + + property<T> &coerce(const typename property<T>::coercer_type &coercer){ + _coercer = coercer; + return *this; + } + + property<T> &publish(const typename property<T>::publisher_type &publisher){ + _publisher = publisher; + return *this; + } + + property<T> &subscribe(const typename property<T>::subscriber_type &subscriber){ + _subscribers.push_back(subscriber); + return *this; + } + + property<T> &update(void){ + this->set(this->get()); + return *this; + } + + property<T> &set(const T &value){ + _value = boost::shared_ptr<T>(new T(_coercer.empty()? value : _coercer(value))); + BOOST_FOREACH(typename property<T>::subscriber_type &subscriber, _subscribers){ + subscriber(*_value); //let errors propagate + } + return *this; + } + + T get(void) const{ + if (empty()) throw uhd::runtime_error("Cannot get() on an empty property"); + return _publisher.empty()? *_value : _publisher(); + } + + bool empty(void) const{ + return _publisher.empty() and _value.get() == NULL; + } + +private: + std::vector<typename property<T>::subscriber_type> _subscribers; + typename property<T>::publisher_type _publisher; + typename property<T>::coercer_type _coercer; + boost::shared_ptr<T> _value; +}; + +}} //namespace uhd::/*anon*/ + +/*********************************************************************** + * Implement templated methods for the property tree + **********************************************************************/ +namespace uhd{ + + template <typename T> property<T> &property_tree::create(const fs_path &path){ + this->_create(path, typename boost::shared_ptr<property<T> >(new property_impl<T>())); + return this->access<T>(path); + } + + template <typename T> property<T> &property_tree::access(const fs_path &path){ + return *boost::static_pointer_cast<property<T> >(this->_access(path)); + } + +} //namespace uhd + +#endif /* INCLUDED_UHD_PROPERTY_TREE_IPP */ diff --git a/host/include/uhd/stream.hpp b/host/include/uhd/stream.hpp new file mode 100644 index 000000000..1fb846955 --- /dev/null +++ b/host/include/uhd/stream.hpp @@ -0,0 +1,224 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_STREAM_HPP +#define INCLUDED_UHD_STREAM_HPP + +#include <uhd/config.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/ref_vector.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <vector> +#include <string> + +namespace uhd{ + +/*! + * A struct of parameters to construct a streamer. + * + * Note: + * Not all combinations of CPU and OTW format have conversion support. + * You may however write and register your own conversion routines. + */ +struct UHD_API stream_args_t{ + + //! Convenience constructor for streamer args + stream_args_t( + const std::string &cpu = "", + const std::string &otw = "" + ){ + cpu_format = cpu; + otw_format = otw; + } + + /*! + * The CPU format is a string that describes the format of host memory. + * Conversions for the following CPU formats have been implemented: + * - fc64 - complex<double> + * - fc32 - complex<float> + * - sc16 - complex<int16_t> + * + * The following are not implemented, but are listed to demonstrate naming convention: + * - sc8 - complex<int8_t> + * - f32 - float + * - f64 - double + * - s16 - int16_t + * - s8 - int8_t + */ + std::string cpu_format; + + /*! + * The OTW format is a string that describes the format over-the-wire. + * The following over-the-wire formats have been implemented: + * - sc16 - Q16 I16 + * - sc8 - Q8_1 I8_1 Q8_0 I8_0 + * + * The following are not implemented, but are listed to demonstrate naming convention: + * - s16 - R16_1 R16_0 + * - s8 - R8_3 R8_2 R8_1 R8_0 + */ + std::string otw_format; + + /*! + * The args parameter is used to pass arbitrary key/value pairs. + * Possible keys used by args (depends on implementation): + * + * - fullscale: specifies the full-scale amplitude when using floats. + * By default, the fullscale amplitude under floating point is 1.0. + * Set the "fullscale" to scale the samples in the host to the + * expected input range and/or output range of your application. + * + * - peak: specifies a fractional sample level to calculate scaling with the sc8 wire format. + * When using sc8 samples over the wire, the device must scale samples + * (both on the host and in the device) to satisfy the dynamic range needs. + * The peak value specifies a fraction of the maximum sample level (1.0 = 100%). + * Set peak to max_sample_level/full_scale_level to ensure optimum dynamic range. + * + * - underflow_policy: how the TX DSP should recover from underflow. + * Possible options are "next_burst" or "next_packet". + * In the "next_burst" mode, the DSP drops incoming packets until a new burst is started. + * In the "next_packet" mode, the DSP starts transmitting again at the next packet. + * + * - spp: (samples per packet) controls the size of RX packets. + * When not specified, the packets are always maximum frame size. + * Users should specify this option to request smaller than default + * packets, probably with the intention of reducing packet latency. + * + * The following are not implemented, but are listed for conceptual purposes: + * - function: magnitude or phase/magnitude + * - units: numeric units like counts or dBm + */ + device_addr_t args; + + /*! + * The channels is a list of channel numbers. + * Leave this blank to default to channel 0. + * Set channels for a multi-channel application. + * Channel mapping depends on the front-end selection. + */ + std::vector<size_t> channels; +}; + +/*! + * The RX streamer is the host interface to receiving samples. + * It represents the layer between the samples on the host + * and samples inside the device's receive DSP processing. + */ +class UHD_API rx_streamer : boost::noncopyable{ +public: + typedef boost::shared_ptr<rx_streamer> sptr; + + //! Get the number of channels associated with this streamer + virtual size_t get_num_channels(void) const = 0; + + //! Get the max number of samples per buffer per packet + virtual size_t get_max_num_samps(void) const = 0; + + //! Typedef for a pointer to a single, or a collection of recv buffers + typedef ref_vector<void *> buffs_type; + + /*! + * Receive buffers containing samples described by the metadata. + * + * Receive handles fragmentation as follows: + * If the buffer has insufficient space to hold all samples + * that were received in a single packet over-the-wire, + * then the buffer will be completely filled and the implementation + * will hold a pointer into the remaining portion of the packet. + * Subsequent calls will load from the remainder of the packet, + * and will flag the metadata to show that this is a fragment. + * The next call to receive, after the remainder becomes exahausted, + * will perform an over-the-wire receive as usual. + * See the rx metadata fragment flags and offset fields for details. + * + * This is a blocking call and will not return until the number + * of samples returned have been written into each buffer. + * Under a timeout condition, the number of samples returned + * may be less than the number of samples specified. + * + * The one_packet option allows the user to guarantee that + * the call will return after a single packet has been processed. + * This may be useful to maintain packet boundaries in some cases. + * + * \param buffs a vector of writable memory to fill with samples + * \param nsamps_per_buff the size of each buffer in number of samples + * \param metadata data to fill describing the buffer + * \param timeout the timeout in seconds to wait for a packet + * \param one_packet return after the first packet is received + * \return the number of samples received or 0 on error + */ + virtual size_t recv( + const buffs_type &buffs, + const size_t nsamps_per_buff, + rx_metadata_t &metadata, + const double timeout = 0.1, + const bool one_packet = false + ) = 0; +}; + +/*! + * The TX streamer is the host interface to transmitting samples. + * It represents the layer between the samples on the host + * and samples inside the device's transmit DSP processing. + */ +class UHD_API tx_streamer : boost::noncopyable{ +public: + typedef boost::shared_ptr<tx_streamer> sptr; + + //! Get the number of channels associated with this streamer + virtual size_t get_num_channels(void) const = 0; + + //! Get the max number of samples per buffer per packet + virtual size_t get_max_num_samps(void) const = 0; + + //! Typedef for a pointer to a single, or a collection of send buffers + typedef ref_vector<const void *> buffs_type; + + /*! + * Send buffers containing samples described by the metadata. + * + * Send handles fragmentation as follows: + * If the buffer has more items than the maximum per packet, + * the send method will fragment the samples across several packets. + * Send will respect the burst flags when fragmenting to ensure + * that start of burst can only be set on the first fragment and + * that end of burst can only be set on the final fragment. + * + * This is a blocking call and will not return until the number + * of samples returned have been read out of each buffer. + * Under a timeout condition, the number of samples returned + * may be less than the number of samples specified. + * + * \param buffs a vector of read-only memory containing samples + * \param nsamps_per_buff the number of samples to send, per buffer + * \param metadata data describing the buffer's contents + * \param timeout the timeout in seconds to wait on a packet + * \return the number of samples sent + */ + virtual size_t send( + const buffs_type &buffs, + const size_t nsamps_per_buff, + const tx_metadata_t &metadata, + const double timeout = 0.1 + ) = 0; +}; + +} //namespace uhd + +#endif /* INCLUDED_UHD_STREAM_HPP */ diff --git a/host/include/uhd/transport/CMakeLists.txt b/host/include/uhd/transport/CMakeLists.txt new file mode 100644 index 000000000..bf7497ee7 --- /dev/null +++ b/host/include/uhd/transport/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + + +INSTALL(FILES + bounded_buffer.hpp + bounded_buffer.ipp + buffer_pool.hpp + if_addrs.hpp + udp_simple.hpp + udp_zero_copy.hpp + usb_control.hpp + usb_zero_copy.hpp + usb_device_handle.hpp + vrt_if_packet.hpp + zero_copy.hpp + DESTINATION ${INCLUDE_DIR}/uhd/transport + COMPONENT headers +) diff --git a/host/include/uhd/transport/bounded_buffer.hpp b/host/include/uhd/transport/bounded_buffer.hpp new file mode 100644 index 000000000..620a708d0 --- /dev/null +++ b/host/include/uhd/transport/bounded_buffer.hpp @@ -0,0 +1,121 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_BOUNDED_BUFFER_HPP +#define INCLUDED_UHD_TRANSPORT_BOUNDED_BUFFER_HPP + +#include <uhd/transport/bounded_buffer.ipp> //detail + +namespace uhd{ namespace transport{ + + /*! + * Implement a templated bounded buffer: + * Used for passing elements between threads in a producer-consumer model. + * The bounded buffer implemented waits and timed waits with condition variables. + * The pop operation blocks on the bounded_buffer to become non empty. + * The push operation blocks on the bounded_buffer to become non full. + */ + template <typename elem_type> class bounded_buffer{ + public: + + /*! + * Create a new bounded buffer object. + * \param capacity the bounded_buffer capacity + */ + bounded_buffer(size_t capacity): + _detail(capacity) + { + /* NOP */ + } + + /*! + * Push a new element into the bounded buffer immediately. + * The element will not be pushed when the buffer is full. + * \param elem the element reference pop to + * \return false when the buffer is full + */ + UHD_INLINE bool push_with_haste(const elem_type &elem){ + return _detail.push_with_haste(elem); + } + + /*! + * Push a new element into the bounded buffer. + * If the buffer is full prior to the push, + * make room by poping the oldest element. + * \param elem the new element to push + * \return true if the element fit without popping for space + */ + UHD_INLINE bool push_with_pop_on_full(const elem_type &elem){ + return _detail.push_with_pop_on_full(elem); + } + + /*! + * Push a new element into the bounded_buffer. + * Wait until the bounded_buffer becomes non-full. + * \param elem the new element to push + */ + UHD_INLINE void push_with_wait(const elem_type &elem){ + return _detail.push_with_wait(elem); + } + + /*! + * Push a new element into the bounded_buffer. + * Wait until the bounded_buffer becomes non-full or timeout. + * \param elem the new element to push + * \param timeout the timeout in seconds + * \return false when the operation times out + */ + UHD_INLINE bool push_with_timed_wait(const elem_type &elem, double timeout){ + return _detail.push_with_timed_wait(elem, timeout); + } + + /*! + * Pop an element from the bounded buffer immediately. + * The element will not be popped when the buffer is empty. + * \param elem the element reference pop to + * \return false when the buffer is empty + */ + UHD_INLINE bool pop_with_haste(elem_type &elem){ + return _detail.pop_with_haste(elem); + } + + /*! + * Pop an element from the bounded_buffer. + * Wait until the bounded_buffer becomes non-empty. + * \param elem the element reference pop to + */ + UHD_INLINE void pop_with_wait(elem_type &elem){ + return _detail.pop_with_wait(elem); + } + + /*! + * Pop an element from the bounded_buffer. + * Wait until the bounded_buffer becomes non-empty or timeout. + * \param elem the element reference pop to + * \param timeout the timeout in seconds + * \return false when the operation times out + */ + UHD_INLINE bool pop_with_timed_wait(elem_type &elem, double timeout){ + return _detail.pop_with_timed_wait(elem, timeout); + } + + private: bounded_buffer_detail<elem_type> _detail; + }; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_BOUNDED_BUFFER_HPP */ diff --git a/host/include/uhd/transport/bounded_buffer.ipp b/host/include/uhd/transport/bounded_buffer.ipp new file mode 100644 index 000000000..9c24005b7 --- /dev/null +++ b/host/include/uhd/transport/bounded_buffer.ipp @@ -0,0 +1,147 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_BOUNDED_BUFFER_IPP +#define INCLUDED_UHD_TRANSPORT_BOUNDED_BUFFER_IPP + +#include <uhd/config.hpp> +#include <boost/bind.hpp> +#include <boost/utility.hpp> +#include <boost/function.hpp> +#include <boost/circular_buffer.hpp> +#include <boost/thread/condition.hpp> +#include <boost/thread/locks.hpp> + +namespace uhd{ namespace transport{ namespace{ /*anon*/ + + template <typename elem_type> class bounded_buffer_detail : boost::noncopyable{ + public: + + bounded_buffer_detail(size_t capacity): + _buffer(capacity) + { + _not_full_fcn = boost::bind(&bounded_buffer_detail<elem_type>::not_full, this); + _not_empty_fcn = boost::bind(&bounded_buffer_detail<elem_type>::not_empty, this); + } + + UHD_INLINE bool push_with_haste(const elem_type &elem){ + boost::mutex::scoped_lock lock(_mutex); + if (_buffer.full()) return false; + _buffer.push_front(elem); + lock.unlock(); + _empty_cond.notify_one(); + return true; + } + + UHD_INLINE bool push_with_pop_on_full(const elem_type &elem){ + boost::mutex::scoped_lock lock(_mutex); + if (_buffer.full()){ + _buffer.pop_back(); + _buffer.push_front(elem); + lock.unlock(); + _empty_cond.notify_one(); + return false; + } + else{ + _buffer.push_front(elem); + lock.unlock(); + _empty_cond.notify_one(); + return true; + } + } + + UHD_INLINE void push_with_wait(const elem_type &elem){ + if (this->push_with_haste(elem)) return; + boost::mutex::scoped_lock lock(_mutex); + _full_cond.wait(lock, _not_full_fcn); + _buffer.push_front(elem); + lock.unlock(); + _empty_cond.notify_one(); + } + + UHD_INLINE bool push_with_timed_wait(const elem_type &elem, double timeout){ + if (this->push_with_haste(elem)) return true; + boost::mutex::scoped_lock lock(_mutex); + if (not _full_cond.timed_wait( + lock, to_time_dur(timeout), _not_full_fcn + )) return false; + _buffer.push_front(elem); + lock.unlock(); + _empty_cond.notify_one(); + return true; + } + + UHD_INLINE bool pop_with_haste(elem_type &elem){ + boost::mutex::scoped_lock lock(_mutex); + if (_buffer.empty()) return false; + this->pop_back(elem); + lock.unlock(); + _full_cond.notify_one(); + return true; + } + + UHD_INLINE void pop_with_wait(elem_type &elem){ + if (this->pop_with_haste(elem)) return; + boost::mutex::scoped_lock lock(_mutex); + _empty_cond.wait(lock, _not_empty_fcn); + this->pop_back(elem); + lock.unlock(); + _full_cond.notify_one(); + } + + UHD_INLINE bool pop_with_timed_wait(elem_type &elem, double timeout){ + if (this->pop_with_haste(elem)) return true; + boost::mutex::scoped_lock lock(_mutex); + if (not _empty_cond.timed_wait( + lock, to_time_dur(timeout), _not_empty_fcn + )) return false; + this->pop_back(elem); + lock.unlock(); + _full_cond.notify_one(); + return true; + } + + private: + boost::mutex _mutex; + boost::condition _empty_cond, _full_cond; + boost::circular_buffer<elem_type> _buffer; + + bool not_full(void) const{return not _buffer.full();} + bool not_empty(void) const{return not _buffer.empty();} + + boost::function<bool(void)> _not_full_fcn, _not_empty_fcn; + + /*! + * Three part operation to pop an element: + * 1) assign elem to the back element + * 2) assign the back element to empty + * 3) pop the back to move the counter + */ + UHD_INLINE void pop_back(elem_type &elem){ + elem = _buffer.back(); + _buffer.back() = elem_type(); + _buffer.pop_back(); + } + + static UHD_INLINE boost::posix_time::time_duration to_time_dur(double timeout){ + return boost::posix_time::microseconds(long(timeout*1e6)); + } + + }; +}}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_BOUNDED_BUFFER_IPP */ diff --git a/host/include/uhd/transport/buffer_pool.hpp b/host/include/uhd/transport/buffer_pool.hpp new file mode 100644 index 000000000..84a338097 --- /dev/null +++ b/host/include/uhd/transport/buffer_pool.hpp @@ -0,0 +1,59 @@ +// +// Copyright 2011-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_BUFFER_POOL_HPP +#define INCLUDED_UHD_TRANSPORT_BUFFER_POOL_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> + +namespace uhd{ namespace transport{ + + /*! + * A buffer pool manages memory for a homogeneous set of buffers. + * Each buffer is the pool start at a 16-byte alignment boundary. + */ + class UHD_API buffer_pool : boost::noncopyable{ + public: + typedef boost::shared_ptr<buffer_pool> sptr; + typedef void * ptr_type; + + /*! + * Make a new buffer pool. + * \param num_buffs the number of buffers to allocate + * \param buff_size the size of each buffer in bytes + * \param alignment the alignment boundary in bytes + * \return a new buffer pool buff_size X num_buffs + */ + static sptr make( + const size_t num_buffs, + const size_t buff_size, + const size_t alignment = 16 + ); + + //! Get a pointer to the buffer start at the specified index + virtual ptr_type at(const size_t index) const = 0; + + //! Get the number of buffers in this pool + virtual size_t size(void) const = 0; + }; + +}} //namespace + + +#endif /* INCLUDED_UHD_TRANSPORT_BUFFER_POOL_HPP */ diff --git a/host/include/uhd/transport/if_addrs.hpp b/host/include/uhd/transport/if_addrs.hpp new file mode 100644 index 000000000..689aff42c --- /dev/null +++ b/host/include/uhd/transport/if_addrs.hpp @@ -0,0 +1,46 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_IF_ADDRS_HPP +#define INCLUDED_UHD_TRANSPORT_IF_ADDRS_HPP + +#include <uhd/config.hpp> +#include <string> +#include <vector> + +namespace uhd{ namespace transport{ + + /*! + * The address for a network interface. + */ + struct UHD_API if_addrs_t{ + std::string inet; + std::string mask; + std::string bcast; + }; + + /*! + * Get a list of network interface addresses. + * The internal implementation is system-dependent. + * \return a vector of if addrs + */ + UHD_API std::vector<if_addrs_t> get_if_addrs(void); + +}} //namespace + + +#endif /* INCLUDED_UHD_TRANSPORT_IF_ADDRS_HPP */ diff --git a/host/include/uhd/transport/udp_simple.hpp b/host/include/uhd/transport/udp_simple.hpp new file mode 100644 index 000000000..ead3ad4b7 --- /dev/null +++ b/host/include/uhd/transport/udp_simple.hpp @@ -0,0 +1,98 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_UDP_SIMPLE_HPP +#define INCLUDED_UHD_TRANSPORT_UDP_SIMPLE_HPP + +#include <uhd/config.hpp> +#include <uhd/types/serial.hpp> +#include <boost/asio/buffer.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> + +namespace uhd{ namespace transport{ + +class UHD_API udp_simple : boost::noncopyable{ +public: + typedef boost::shared_ptr<udp_simple> sptr; + + //! The maximum number of bytes per udp packet. + static const size_t mtu = 1500 - 20 - 8; //default ipv4 mtu - ipv4 header - udp header + + /*! + * Make a new connected udp transport: + * This transport is for sending and receiving + * between this host and a single endpoint. + * The primary usage for this transport will be control transactions. + * The underlying implementation is simple and portable (not fast). + * + * The address will be resolved, it can be a host name or ipv4. + * The port will be resolved, it can be a port type or number. + * + * \param addr a string representing the destination address + * \param port a string representing the destination port + */ + static sptr make_connected(const std::string &addr, const std::string &port); + + /*! + * Make a new broadcasting udp transport: + * This transport can send udp broadcast datagrams + * and receive datagrams from multiple sources. + * The primary usage for this transport will be to discover devices. + * + * The address will be resolved, it can be a host name or ipv4. + * The port will be resolved, it can be a port type or number. + * + * \param addr a string representing the destination address + * \param port a string representing the destination port + */ + static sptr make_broadcast(const std::string &addr, const std::string &port); + + /*! + * Make a UART interface from a UDP transport. + * \param udp the UDP transport object + * \return a new UART interface + */ + static uart_iface::sptr make_uart(sptr udp); + + /*! + * Send a single buffer. + * Blocks until the data is sent. + * \param buff single asio buffer + * \return the number of bytes sent + */ + virtual size_t send(const boost::asio::const_buffer &buff) = 0; + + /*! + * Receive into the provided buffer. + * Blocks until data is received or a timeout occurs. + * \param buff a mutable buffer to receive into + * \param timeout the timeout in seconds + * \return the number of bytes received or zero on timeout + */ + virtual size_t recv(const boost::asio::mutable_buffer &buff, double timeout = 0.1) = 0; + + /*! + * Get the last IP address as seen by recv(). + * Only use this with the broadcast socket. + */ + virtual std::string get_recv_addr(void) = 0; +}; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_UDP_SIMPLE_HPP */ diff --git a/host/include/uhd/transport/udp_zero_copy.hpp b/host/include/uhd/transport/udp_zero_copy.hpp new file mode 100644 index 000000000..bbba97b21 --- /dev/null +++ b/host/include/uhd/transport/udp_zero_copy.hpp @@ -0,0 +1,65 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_UDP_ZERO_COPY_HPP +#define INCLUDED_UHD_TRANSPORT_UDP_ZERO_COPY_HPP + +#include <uhd/config.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/device_addr.hpp> +#include <boost/shared_ptr.hpp> + +namespace uhd{ namespace transport{ + +/*! + * A zero copy udp transport provides an efficient way to handle data. + * by avoiding the extra copy when recv() or send() is called on the socket. + * Rather, the zero copy transport gives the caller memory references. + * The caller informs the transport when it is finished with the reference. + * + * On linux systems, the zero copy transport can use a kernel packet ring. + * If no platform specific solution is available, make returns a boost asio + * implementation that wraps the functionality around a standard send/recv calls. + */ +class UHD_API udp_zero_copy : public virtual zero_copy_if{ +public: + typedef boost::shared_ptr<udp_zero_copy> sptr; + + /*! + * Make a new zero copy udp transport: + * This transport is for sending and receiving + * between this host and a single endpoint. + * The primary usage for this transport will be data transactions. + * The underlying implementation is fast and platform specific. + * + * The address will be resolved, it can be a host name or ipv4. + * The port will be resolved, it can be a port type or number. + * + * \param addr a string representing the destination address + * \param port a string representing the destination port + * \param hints optional parameters to pass to the underlying transport + */ + static sptr make( + const std::string &addr, + const std::string &port, + const device_addr_t &hints = device_addr_t() + ); +}; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_UDP_ZERO_COPY_HPP */ diff --git a/host/include/uhd/transport/usb_control.hpp b/host/include/uhd/transport/usb_control.hpp new file mode 100644 index 000000000..2af4b3bbe --- /dev/null +++ b/host/include/uhd/transport/usb_control.hpp @@ -0,0 +1,66 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_USB_CONTROL_HPP +#define INCLUDED_UHD_TRANSPORT_USB_CONTROL_HPP + +#include <uhd/transport/usb_device_handle.hpp> + +namespace uhd { namespace transport { + +class UHD_API usb_control : boost::noncopyable { +public: + typedef boost::shared_ptr<usb_control> sptr; + + /*! + * Create a new usb control transport: + * This transport is for sending and receiving control information from + * the host to device using the Default Control Pipe. + * + * \param handle a device handle that uniquely identifies a USB device + * \param interface the USB interface number for the control transport + */ + static sptr make(usb_device_handle::sptr handle, const size_t interface); + + /*! + * Submit a USB device request: + * Blocks until the request returns + * + * For format and corresponding USB request fields + * see USB Specification Revision 2.0 - 9.3 USB Device Requests + * + * Usage is device specific + * + * \param request_type 1-byte bitmask (bmRequestType) + * \param request 1-byte (bRequest) + * \param value 2-byte (wValue) + * \param index 2-byte (wIndex) + * \param buff buffer to hold send or receive data + * \param length 2-byte (wLength) + * \return number of bytes submitted or error code + */ + virtual ssize_t submit(boost::uint8_t request_type, + boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) = 0; +}; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_USB_CONTROL_HPP */ diff --git a/host/include/uhd/transport/usb_device_handle.hpp b/host/include/uhd/transport/usb_device_handle.hpp new file mode 100644 index 000000000..6f8d868be --- /dev/null +++ b/host/include/uhd/transport/usb_device_handle.hpp @@ -0,0 +1,73 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_USB_DEVICE_HANDLE_HPP +#define INCLUDED_UHD_TRANSPORT_USB_DEVICE_HANDLE_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/cstdint.hpp> +#include <vector> + +namespace uhd { namespace transport { + +/*! + * Device handle class that represents a USB device + * Used for identifying devices on the USB bus and selecting which device is + * used when creating a USB transport. A minimal subset of USB descriptor + * fields are used. Fields can be found in the USB 2.0 specification Table + * 9-8 (Standard Device Descriptor). In addition to fields of the device + * descriptor, the interface returns the device's USB device address. + * + * Note: The USB 2.0 Standard Device Descriptor contains an index rather then + * a true descriptor serial number string. This interface returns the + * actual string descriptor. + */ +class UHD_API usb_device_handle : boost::noncopyable { +public: + typedef boost::shared_ptr<usb_device_handle> sptr; + + /*! + * Return the device's serial number + * \return a string describing the device's serial number + */ + virtual std::string get_serial() const = 0; + + /*! + * Return the device's Vendor ID (usually assigned by the USB-IF) + * \return a Vendor ID + */ + virtual boost::uint16_t get_vendor_id() const = 0; + + /*! + * Return the device's Product ID (usually assigned by manufacturer) + * \return a Product ID + */ + virtual boost::uint16_t get_product_id() const = 0; + + /*! + * Return a vector of USB devices on this host + * \return a vector of USB device handles that match vid and pid + */ + static std::vector<usb_device_handle::sptr> get_device_list(boost::uint16_t vid, boost::uint16_t pid); + +}; //namespace usb + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_USB_DEVICE_HANDLE_HPP */ diff --git a/host/include/uhd/transport/usb_zero_copy.hpp b/host/include/uhd/transport/usb_zero_copy.hpp new file mode 100644 index 000000000..24b709ec9 --- /dev/null +++ b/host/include/uhd/transport/usb_zero_copy.hpp @@ -0,0 +1,86 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_USB_ZERO_COPY_HPP +#define INCLUDED_UHD_TRANSPORT_USB_ZERO_COPY_HPP + +#include <uhd/transport/usb_device_handle.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/device_addr.hpp> + +namespace uhd { namespace transport { + +/*! + * A zero copy usb transport provides an efficient way to handle data. + * by avoiding the extra copy when recv() or send() is called on the handle. + * Rather, the zero copy transport gives the caller memory references. + * The caller informs the transport when it is finished with the reference. + * + * On linux systems, the zero copy transport can use a kernel packet ring. + * If no platform specific solution is available, make returns a boost asio + * implementation that wraps functionality around standard send/recv calls. + */ +class UHD_API usb_zero_copy : public virtual zero_copy_if { +public: + typedef boost::shared_ptr<usb_zero_copy> sptr; + + /*! + * Make a new zero copy usb transport: + * This transport is for sending and receiving between the host + * and a pair of USB bulk transfer endpoints. + * The primary usage for this transport is data transactions. + * The underlying implementation may be platform specific. + * + * \param handle a device handle that uniquely identifying the device + * \param recv_interface an integer specifiying an IN interface number + * \param recv_endpoint an integer specifiying an IN endpoint number + * \param send_interface an integer specifiying an OUT interface number + * \param send_endpoint an integer specifiying an OUT endpoint number + * \param hints optional parameters to pass to the underlying transport + * \return a new zero copy usb object + */ + static sptr make( + usb_device_handle::sptr handle, + const size_t recv_interface, + const size_t recv_endpoint, + const size_t send_interface, + const size_t send_endpoint, + const device_addr_t &hints = device_addr_t() + ); + + /*! + * Make a wrapper around a zero copy implementation. + * The wrapper performs the following functions: + * - Pad commits to the frame boundary + * - Extract multiple packets on recv + * + * When enable multiple receive packets is set to true, + * the implementation inspects the vita length on transfers, + * and may split a single transfer into multiple managed buffers. + * + * \param usb_zc a usb zero copy interface object + * \param usb_frame_boundary bytes per frame + * \return a new zero copy wrapper object + */ + static sptr make_wrapper( + sptr usb_zc, size_t usb_frame_boundary = 512 + ); +}; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_USB_ZERO_COPY_HPP */ diff --git a/host/include/uhd/transport/vrt_if_packet.hpp b/host/include/uhd/transport/vrt_if_packet.hpp new file mode 100644 index 000000000..1be480874 --- /dev/null +++ b/host/include/uhd/transport/vrt_if_packet.hpp @@ -0,0 +1,107 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_VRT_IF_PACKET_HPP +#define INCLUDED_UHD_TRANSPORT_VRT_IF_PACKET_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <cstddef> //size_t + +namespace uhd{ namespace transport{ + +namespace vrt{ + + //! The maximum number of 32-bit words in a vrt if packet header + static const size_t max_if_hdr_words32 = 7; //hdr+sid+cid+tsi+tsf + + /*! + * Definition for fields that can be packed into a vrt if header. + * The size fields are used for input and output depending upon + * the operation used (ie the pack or unpack function call). + */ + struct UHD_API if_packet_info_t{ + //packet type (pack only supports data) + enum packet_type_t { + PACKET_TYPE_DATA = 0x0, + PACKET_TYPE_EXTENSION = 0x1, + PACKET_TYPE_CONTEXT = 0x2 + } packet_type; + + //size fields + size_t num_payload_words32; //required in pack, derived in unpack + size_t num_payload_bytes; //required in pack, derived in unpack + size_t num_header_words32; //derived in pack, derived in unpack + size_t num_packet_words32; //derived in pack, required in unpack + + //header fields + size_t packet_count; + bool sob, eob; + + //optional fields + bool has_sid; boost::uint32_t sid; + bool has_cid; boost::uint64_t cid; + bool has_tsi; boost::uint32_t tsi; + bool has_tsf; boost::uint64_t tsf; + bool has_tlr; boost::uint32_t tlr; + }; + + /*! + * Pack a vrt header from metadata (big endian format). + * \param packet_buff memory to write the packed vrt header + * \param if_packet_info the if packet info (read/write) + */ + UHD_API void if_hdr_pack_be( + boost::uint32_t *packet_buff, + if_packet_info_t &if_packet_info + ); + + /*! + * Unpack a vrt header to metadata (big endian format). + * \param packet_buff memory to read the packed vrt header + * \param if_packet_info the if packet info (read/write) + */ + UHD_API void if_hdr_unpack_be( + const boost::uint32_t *packet_buff, + if_packet_info_t &if_packet_info + ); + + /*! + * Pack a vrt header from metadata (little endian format). + * \param packet_buff memory to write the packed vrt header + * \param if_packet_info the if packet info (read/write) + */ + UHD_API void if_hdr_pack_le( + boost::uint32_t *packet_buff, + if_packet_info_t &if_packet_info + ); + + /*! + * Unpack a vrt header to metadata (little endian format). + * \param packet_buff memory to read the packed vrt header + * \param if_packet_info the if packet info (read/write) + */ + UHD_API void if_hdr_unpack_le( + const boost::uint32_t *packet_buff, + if_packet_info_t &if_packet_info + ); + +} //namespace vrt + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_VRT_IF_PACKET_HPP */ diff --git a/host/include/uhd/transport/zero_copy.hpp b/host/include/uhd/transport/zero_copy.hpp new file mode 100644 index 000000000..f80c738aa --- /dev/null +++ b/host/include/uhd/transport/zero_copy.hpp @@ -0,0 +1,184 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_ZERO_COPY_HPP +#define INCLUDED_UHD_TRANSPORT_ZERO_COPY_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> + +namespace uhd{ namespace transport{ + + //! Create smart pointer to a reusable managed buffer + template <typename T> UHD_INLINE boost::intrusive_ptr<T> make_managed_buffer(T *p){ + p->_ref_count = 1; //reset the count to 1 reference + return boost::intrusive_ptr<T>(p, false); + } + + /*! + * A managed receive buffer: + * Contains a reference to transport-managed memory, + * and a method to release the memory after reading. + */ + class UHD_API managed_recv_buffer{ + public: + typedef boost::intrusive_ptr<managed_recv_buffer> sptr; + + /*! + * Signal to the transport that we are done with the buffer. + * This should be called to release the buffer to the transport object. + * After calling, the referenced memory should be considered invalid. + */ + virtual void release(void) = 0; + + /*! + * Get a pointer to the underlying buffer. + * \return a pointer into memory + */ + template <class T> inline T cast(void) const{ + return static_cast<T>(this->get_buff()); + } + + /*! + * Get the size of the underlying buffer. + * \return the number of bytes + */ + inline size_t size(void) const{ + return this->get_size(); + } + + private: + virtual const void *get_buff(void) const = 0; + virtual size_t get_size(void) const = 0; + + public: int _ref_count; + }; + + UHD_INLINE void intrusive_ptr_add_ref(managed_recv_buffer *p){ + ++(p->_ref_count); + } + + UHD_INLINE void intrusive_ptr_release(managed_recv_buffer *p){ + if (--(p->_ref_count) == 0) p->release(); + } + + /*! + * A managed send buffer: + * Contains a reference to transport-managed memory, + * and a method to commit the memory after writing. + */ + class UHD_API managed_send_buffer{ + public: + typedef boost::intrusive_ptr<managed_send_buffer> sptr; + + /*! + * Signal to the transport that we are done with the buffer. + * This should be called to commit the write to the transport object. + * After calling, the referenced memory should be considered invalid. + * \param num_bytes the number of bytes written into the buffer + */ + virtual void commit(size_t num_bytes) = 0; + + /*! + * Get a pointer to the underlying buffer. + * \return a pointer into memory + */ + template <class T> inline T cast(void) const{ + return static_cast<T>(this->get_buff()); + } + + /*! + * Get the size of the underlying buffer. + * \return the number of bytes + */ + inline size_t size(void) const{ + return this->get_size(); + } + + private: + virtual void *get_buff(void) const = 0; + virtual size_t get_size(void) const = 0; + + public: int _ref_count; + }; + + UHD_INLINE void intrusive_ptr_add_ref(managed_send_buffer *p){ + ++(p->_ref_count); + } + + UHD_INLINE void intrusive_ptr_release(managed_send_buffer *p){ + if (--(p->_ref_count) == 0) p->commit(0); + } + + /*! + * A zero-copy interface for transport objects. + * Provides a way to get send and receive buffers + * with memory managed by the transport object. + */ + class UHD_API zero_copy_if : boost::noncopyable{ + public: + typedef boost::shared_ptr<zero_copy_if> sptr; + + /*! + * Get a new receive buffer from this transport object. + * \param timeout the timeout to get the buffer in seconds + * \return a managed buffer, or null sptr on timeout/error + */ + virtual managed_recv_buffer::sptr get_recv_buff(double timeout = 0.1) = 0; + + /*! + * Get the number of receive frames: + * The number of simultaneous receive buffers in use. + * \return number of frames + */ + virtual size_t get_num_recv_frames(void) const = 0; + + /*! + * Get the size of a receive frame: + * The maximum capacity of a single receive buffer. + * \return frame size in bytes + */ + virtual size_t get_recv_frame_size(void) const = 0; + + /*! + * Get a new send buffer from this transport object. + * \param timeout the timeout to get the buffer in seconds + * \return a managed buffer, or null sptr on timeout/error + */ + virtual managed_send_buffer::sptr get_send_buff(double timeout = 0.1) = 0; + + /*! + * Get the number of send frames: + * The number of simultaneous send buffers in use. + * \return number of frames + */ + virtual size_t get_num_send_frames(void) const = 0; + + /*! + * Get the size of a send frame: + * The maximum capacity of a single send buffer. + * \return frame size in bytes + */ + virtual size_t get_send_frame_size(void) const = 0; + + }; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_ZERO_COPY_HPP */ diff --git a/host/include/uhd/types/CMakeLists.txt b/host/include/uhd/types/CMakeLists.txt new file mode 100644 index 000000000..0971ca472 --- /dev/null +++ b/host/include/uhd/types/CMakeLists.txt @@ -0,0 +1,38 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + + +INSTALL(FILES + clock_config.hpp + device_addr.hpp + dict.ipp + dict.hpp + io_type.hpp + mac_addr.hpp + metadata.hpp + otw_type.hpp + ranges.hpp + ref_vector.hpp + sensors.hpp + serial.hpp + stream_cmd.hpp + time_spec.hpp + tune_request.hpp + tune_result.hpp + DESTINATION ${INCLUDE_DIR}/uhd/types + COMPONENT headers +) diff --git a/host/include/uhd/types/clock_config.hpp b/host/include/uhd/types/clock_config.hpp new file mode 100644 index 000000000..27b312245 --- /dev/null +++ b/host/include/uhd/types/clock_config.hpp @@ -0,0 +1,66 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_CLOCK_CONFIG_HPP +#define INCLUDED_UHD_TYPES_CLOCK_CONFIG_HPP + +#include <uhd/config.hpp> + +namespace uhd{ + + /*! + * The DEPRECATED Clock configuration settings: + * The source for the 10MHz reference clock. + * The source and polarity for the PPS clock. + * + * Deprecated in favor of set time/clock source calls. + * Its still in this file for the sake of gr-uhd swig. + * + * Use the convenience functions external() and internal(), + * unless you have a special purpose and cannot use them. + */ + struct UHD_API clock_config_t{ + //------ simple usage --------// + + //! A convenience function to create an external clock configuration + static clock_config_t external(void); + + //! A convenience function to create an internal clock configuration + static clock_config_t internal(void); + + //------ advanced usage --------// + enum ref_source_t { + REF_AUTO = int('a'), //automatic (device specific) + REF_INT = int('i'), //internal reference + REF_SMA = int('s'), //external sma port + REF_MIMO = int('m'), //reference from mimo cable + } ref_source; + enum pps_source_t { + PPS_INT = int('i'), //there is no internal + PPS_SMA = int('s'), //external sma port + PPS_MIMO = int('m'), //time sync from mimo cable + } pps_source; + enum pps_polarity_t { + PPS_NEG = int('n'), //negative edge + PPS_POS = int('p') //positive edge + } pps_polarity; + clock_config_t(void); + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_CLOCK_CONFIG_HPP */ diff --git a/host/include/uhd/types/device_addr.hpp b/host/include/uhd/types/device_addr.hpp new file mode 100644 index 000000000..2c0841146 --- /dev/null +++ b/host/include/uhd/types/device_addr.hpp @@ -0,0 +1,98 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_DEVICE_ADDR_HPP +#define INCLUDED_UHD_TYPES_DEVICE_ADDR_HPP + +#include <uhd/config.hpp> +#include <uhd/types/dict.hpp> +#include <boost/lexical_cast.hpp> +#include <stdexcept> +#include <vector> +#include <string> + +namespace uhd{ + + /*! + * Mapping of key/value pairs for locating devices on the system. + * When left empty, the device discovery routines will search + * all available transports on the system (ethernet, usb...). + * + * To narrow down the discovery process to a particular device, + * specify a transport key/value pair specific to your device. + * - Ex, to find a usrp2: my_dev_addr["addr"] = [resolvable_hostname_or_ip] + * + * The device address can also be used to pass arguments into + * the transport layer control to set (for example) buffer sizes. + * + * An arguments string, is a way to represent a device address + * using a single string with delimiter characters. + * - Ex: addr=192.168.10.2 + * - Ex: addr=192.168.10.2, recv_buff_size=1e6 + */ + class UHD_API device_addr_t : public dict<std::string, std::string>{ + public: + /*! + * Create a device address from an args string. + * \param args the arguments string + */ + device_addr_t(const std::string &args = ""); + + /*! + * Convert a device address into a pretty print string. + * \return a printable string representing the device address + */ + std::string to_pp_string(void) const; + + /*! + * Convert the device address into an args string. + * The args string contains delimiter symbols. + * \return a string with delimiter markup + */ + std::string to_string(void) const; + + /*! + * Lexically cast a parameter to the specified type, + * or use the default value if the key is not found. + * \param key the key as one of the address parameters + * \param def the value to use when key is not present + * \return the casted value as type T or the default + * \throw error when the parameter cannot be casted + */ + template <typename T> T cast(const std::string &key, const T &def) const{ + if (not this->has_key(key)) return def; + try{ + return boost::lexical_cast<T>((*this)[key]); + } + catch(const boost::bad_lexical_cast &){ + throw std::runtime_error("cannot cast " + key + " = " + (*this)[key]); + } + } + }; + + //! A typedef for a vector of device addresses + typedef std::vector<device_addr_t> device_addrs_t; + + //! Separate an indexed device address into a vector of device addresses + UHD_API device_addrs_t separate_device_addr(const device_addr_t &dev_addr); + + //! Combine a vector of device addresses into an indexed device address + UHD_API device_addr_t combine_device_addrs(const device_addrs_t &dev_addrs); + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_DEVICE_ADDR_HPP */ diff --git a/host/include/uhd/types/dict.hpp b/host/include/uhd/types/dict.hpp new file mode 100644 index 000000000..97fa8f09c --- /dev/null +++ b/host/include/uhd/types/dict.hpp @@ -0,0 +1,129 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_DICT_HPP +#define INCLUDED_UHD_TYPES_DICT_HPP + +#include <uhd/config.hpp> +#include <vector> +#include <list> + +namespace uhd{ + + /*! + * A templated dictionary class with a python-like interface. + */ + template <typename Key, typename Val> class dict{ + public: + /*! + * Create a new empty dictionary. + */ + dict(void); + + /*! + * Input iterator constructor: + * Makes boost::assign::map_list_of work. + * \param first the begin iterator + * \param last the end iterator + */ + template <typename InputIterator> + dict(InputIterator first, InputIterator last); + + /*! + * Get the number of elements in this dict. + * \return the number of elements + */ + std::size_t size(void) const; + + /*! + * Get a list of the keys in this dict. + * Key order depends on insertion precedence. + * \return vector of keys + */ + std::vector<Key> keys(void) const; + + /*! + * Get a list of the values in this dict. + * Value order depends on insertion precedence. + * \return vector of values + */ + std::vector<Val> vals(void) const; + + /*! + * Does the dictionary contain this key? + * \param key the key to look for + * \return true if found + */ + bool has_key(const Key &key) const; + + /*! + * Get a value in the dict or default. + * \param key the key to look for + * \param other use if key not found + * \return the value or default + */ + const Val &get(const Key &key, const Val &other) const; + + /*! + * Get a value in the dict or throw. + * \param key the key to look for + * \return the value or default + */ + const Val &get(const Key &key) const; + + /*! + * Set a value in the dict at the key. + * \param key the key to set at + * \param val the value to set + */ + void set(const Key &key, const Val &val); + + /*! + * Get a value for the given key if it exists. + * If the key is not found throw an error. + * \param key the key to look for + * \return the value at the key + * \throw an exception when not found + */ + const Val &operator[](const Key &key) const; + + /*! + * Set a value for the given key, however, in reality + * it really returns a reference which can be assigned to. + * \param key the key to set to + * \return a reference to the value + */ + Val &operator[](const Key &key); + + /*! + * Pop an item out of the dictionary. + * \param key the item key + * \return the value of the item + * \throw an exception when not found + */ + Val pop(const Key &key); + + private: + typedef std::pair<Key, Val> pair_t; + std::list<pair_t> _map; //private container + }; + +} //namespace uhd + +#include <uhd/types/dict.ipp> + +#endif /* INCLUDED_UHD_TYPES_DICT_HPP */ diff --git a/host/include/uhd/types/dict.ipp b/host/include/uhd/types/dict.ipp new file mode 100644 index 000000000..5e9cf97ad --- /dev/null +++ b/host/include/uhd/types/dict.ipp @@ -0,0 +1,140 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_DICT_IPP +#define INCLUDED_UHD_TYPES_DICT_IPP + +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <typeinfo> + +namespace uhd{ + + namespace /*anon*/{ + template<typename Key, typename Val> + struct key_not_found: uhd::key_error{ + key_not_found(const Key &key): uhd::key_error( + str(boost::format( + "key \"%s\" not found in dict(%s, %s)" + ) % boost::lexical_cast<std::string>(key) + % typeid(Key).name() % typeid(Val).name() + ) + ){ + /* NOP */ + } + }; + } // namespace /*anon*/ + + template <typename Key, typename Val> + dict<Key, Val>::dict(void){ + /* NOP */ + } + + template <typename Key, typename Val> template <typename InputIterator> + dict<Key, Val>::dict(InputIterator first, InputIterator last): + _map(first, last) + { + /* NOP */ + } + + template <typename Key, typename Val> + std::size_t dict<Key, Val>::size(void) const{ + return _map.size(); + } + + template <typename Key, typename Val> + std::vector<Key> dict<Key, Val>::keys(void) const{ + std::vector<Key> keys; + BOOST_FOREACH(const pair_t &p, _map){ + keys.push_back(p.first); + } + return keys; + } + + template <typename Key, typename Val> + std::vector<Val> dict<Key, Val>::vals(void) const{ + std::vector<Val> vals; + BOOST_FOREACH(const pair_t &p, _map){ + vals.push_back(p.second); + } + return vals; + } + + template <typename Key, typename Val> + bool dict<Key, Val>::has_key(const Key &key) const{ + BOOST_FOREACH(const pair_t &p, _map){ + if (p.first == key) return true; + } + return false; + } + + template <typename Key, typename Val> + const Val &dict<Key, Val>::get(const Key &key, const Val &other) const{ + BOOST_FOREACH(const pair_t &p, _map){ + if (p.first == key) return p.second; + } + return other; + } + + template <typename Key, typename Val> + const Val &dict<Key, Val>::get(const Key &key) const{ + BOOST_FOREACH(const pair_t &p, _map){ + if (p.first == key) return p.second; + } + throw key_not_found<Key, Val>(key); + } + + template <typename Key, typename Val> + void dict<Key, Val>::set(const Key &key, const Val &val){ + (*this)[key] = val; + } + + template <typename Key, typename Val> + const Val &dict<Key, Val>::operator[](const Key &key) const{ + BOOST_FOREACH(const pair_t &p, _map){ + if (p.first == key) return p.second; + } + throw key_not_found<Key, Val>(key); + } + + template <typename Key, typename Val> + Val &dict<Key, Val>::operator[](const Key &key){ + BOOST_FOREACH(pair_t &p, _map){ + if (p.first == key) return p.second; + } + _map.push_back(std::make_pair(key, Val())); + return _map.back().second; + } + + template <typename Key, typename Val> + Val dict<Key, Val>::pop(const Key &key){ + typename std::list<pair_t>::iterator it; + for (it = _map.begin(); it != _map.end(); it++){ + if (it->first == key){ + Val val = it->second; + _map.erase(it); + return val; + } + } + throw key_not_found<Key, Val>(key); + } + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_DICT_IPP */ diff --git a/host/include/uhd/types/io_type.hpp b/host/include/uhd/types/io_type.hpp new file mode 100644 index 000000000..967ad7908 --- /dev/null +++ b/host/include/uhd/types/io_type.hpp @@ -0,0 +1,79 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_IO_TYPE_HPP +#define INCLUDED_UHD_TYPES_IO_TYPE_HPP + +#include <uhd/config.hpp> + +namespace uhd{ + + /*! + * The DEPRECATED Input/Output configuration struct: + * Used to specify the IO type with device send/recv. + * + * Deprecated in favor of streamer interface. + * Its still in this file for the sake of gr-uhd swig. + */ + class UHD_API io_type_t{ + public: + + /*! + * Built in IO types known to the system. + */ + enum tid_t{ + //! Custom type (technically unsupported by implementation) + CUSTOM_TYPE = int('?'), + //! Complex floating point (64-bit floats) range [-1.0, +1.0] + COMPLEX_FLOAT64 = int('d'), + //! Complex floating point (32-bit floats) range [-1.0, +1.0] + COMPLEX_FLOAT32 = int('f'), + //! Complex signed integer (16-bit integers) range [-32768, +32767] + COMPLEX_INT16 = int('s'), + //! Complex signed integer (8-bit integers) range [-128, 127] + COMPLEX_INT8 = int('b') + }; + + /*! + * The size of this io type in bytes. + */ + const size_t size; + + /*! + * The type id of this io type. + * Good for using with switch statements. + */ + const tid_t tid; + + /*! + * Create an io type from a built-in type id. + * \param tid a type id known to the system + */ + io_type_t(tid_t tid); + + /*! + * Create an io type from attributes. + * The tid will be set to custom. + * \param size the size in bytes + */ + io_type_t(size_t size); + + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_IO_TYPE_HPP */ diff --git a/host/include/uhd/types/mac_addr.hpp b/host/include/uhd/types/mac_addr.hpp new file mode 100644 index 000000000..0ced2e734 --- /dev/null +++ b/host/include/uhd/types/mac_addr.hpp @@ -0,0 +1,66 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_MAC_ADDR_HPP +#define INCLUDED_UHD_TYPES_MAC_ADDR_HPP + +#include <uhd/config.hpp> +#include <uhd/types/serial.hpp> +#include <string> + +namespace uhd{ + + /*! + * Wrapper for an ethernet mac address. + * Provides conversion between string and binary formats. + */ + class UHD_API mac_addr_t{ + public: + /*! + * Create a mac address a byte array. + * \param bytes a vector of bytes + * \return a new mac address + */ + static mac_addr_t from_bytes(const byte_vector_t &bytes); + + /*! + * Create a mac address from a string. + * \param mac_addr_str the string with delimiters + * \return a new mac address + */ + static mac_addr_t from_string(const std::string &mac_addr_str); + + /*! + * Get the byte representation of the mac address. + * \return a vector of bytes + */ + byte_vector_t to_bytes(void) const; + + /*! + * Get the string representation of this mac address. + * \return a string with delimiters + */ + std::string to_string(void) const; + + private: + mac_addr_t(const byte_vector_t &bytes); //private constructor + const byte_vector_t _bytes; //internal representation + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_MAC_ADDR_HPP */ diff --git a/host/include/uhd/types/metadata.hpp b/host/include/uhd/types/metadata.hpp new file mode 100644 index 000000000..788999900 --- /dev/null +++ b/host/include/uhd/types/metadata.hpp @@ -0,0 +1,163 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_METADATA_HPP +#define INCLUDED_UHD_TYPES_METADATA_HPP + +#include <uhd/config.hpp> +#include <uhd/types/time_spec.hpp> +#include <boost/cstdint.hpp> + +namespace uhd{ + + /*! + * RX metadata structure for describing sent IF data. + * Includes time specification, fragmentation flags, burst flags, and error codes. + * The receive routines will convert IF data headers into metadata. + */ + struct UHD_API rx_metadata_t{ + //! Has time specification? + bool has_time_spec; + + //! Time of the first sample. + time_spec_t time_spec; + + /*! + * Fragmentation flag: + * Similar to IPv4 fragmentation: http://en.wikipedia.org/wiki/IPv4#Fragmentation_and_reassembly + * More fragments is true when the input buffer has insufficient size to fit + * an entire received packet. More fragments will be false for the last fragment. + */ + bool more_fragments; + + /*! + * Fragmentation offset: + * The fragment offset is the sample number at the start of the receive buffer. + * For non-fragmented receives, the fragment offset should always be zero. + */ + size_t fragment_offset; + + //! Start of burst will be true for the first packet in the chain. + bool start_of_burst; + + //! End of burst will be true for the last packet in the chain. + bool end_of_burst; + + /*! + * The error condition on a receive call. + * + * Note: When an overrun occurs in continuous streaming mode, + * the device will continue to send samples to the host. + * For other streaming modes, streaming will discontinue + * until the user issues a new stream command. + * + * The metadata fields have meaning for the following error codes: + * - none + * - late command + * - broken chain + * - overflow + */ + enum error_code_t { + //! No error associated with this metadata. + ERROR_CODE_NONE = 0x0, + //! No packet received, implementation timed-out. + ERROR_CODE_TIMEOUT = 0x1, + //! A stream command was issued in the past. + ERROR_CODE_LATE_COMMAND = 0x2, + //! Expected another stream command. + ERROR_CODE_BROKEN_CHAIN = 0x4, + //! An internal receive buffer has filled. + ERROR_CODE_OVERFLOW = 0x8, + //! Multi-channel alignment failed. + ERROR_CODE_ALIGNMENT = 0xc, + //! The packet could not be parsed. + ERROR_CODE_BAD_PACKET = 0xf + } error_code; + }; + + /*! + * TX metadata structure for describing received IF data. + * Includes time specification, and start and stop burst flags. + * The send routines will convert the metadata to IF data headers. + */ + struct UHD_API tx_metadata_t{ + /*! + * Has time specification? + * - Set false to send immediately. + * - Set true to send at the time specified by time spec. + */ + bool has_time_spec; + + //! When to send the first sample. + time_spec_t time_spec; + + //! Set start of burst to true for the first packet in the chain. + bool start_of_burst; + + //! Set end of burst to true for the last packet in the chain. + bool end_of_burst; + + /*! + * The default constructor: + * Sets the fields to default values (flags set to false). + */ + tx_metadata_t(void); + }; + + /*! + * Async metadata structure for describing transmit related events. + */ + struct UHD_API async_metadata_t{ + //! The channel number in a mimo configuration + size_t channel; + + //! Has time specification? + bool has_time_spec; + + //! When the async event occurred. + time_spec_t time_spec; + + /*! + * The type of event for a receive async message call. + */ + enum event_code_t { + //! A burst was successfully transmitted. + EVENT_CODE_BURST_ACK = 0x1, + //! An internal send buffer has emptied. + EVENT_CODE_UNDERFLOW = 0x2, + //! Packet loss between host and device. + EVENT_CODE_SEQ_ERROR = 0x4, + //! Packet had time that was late. + EVENT_CODE_TIME_ERROR = 0x8, + //! Underflow occurred inside a packet. + EVENT_CODE_UNDERFLOW_IN_PACKET = 0x10, + //! Packet loss within a burst. + EVENT_CODE_SEQ_ERROR_IN_BURST = 0x20, + //! Some kind of custom user payload + EVENT_CODE_USER_PAYLOAD = 0x40 + } event_code; + + /*! + * A special payload populated by custom FPGA fabric. + */ + boost::uint32_t user_payload[4]; + + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_METADATA_HPP */ diff --git a/host/include/uhd/types/otw_type.hpp b/host/include/uhd/types/otw_type.hpp new file mode 100644 index 000000000..6dc634fc8 --- /dev/null +++ b/host/include/uhd/types/otw_type.hpp @@ -0,0 +1,2 @@ +//The OTW type API has been deprecated in favor of the streamer interface +#include <uhd/deprecated.hpp> diff --git a/host/include/uhd/types/ranges.hpp b/host/include/uhd/types/ranges.hpp new file mode 100644 index 000000000..f0d0e1c0b --- /dev/null +++ b/host/include/uhd/types/ranges.hpp @@ -0,0 +1,120 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_RANGES_HPP +#define INCLUDED_UHD_TYPES_RANGES_HPP + +#include <uhd/config.hpp> +#include <uhd/utils/pimpl.hpp> +#include <string> +#include <vector> + +namespace uhd{ + + /*! + * A range object describes a set of discrete values of the form: + * y = start + step*n, where n is an integer between 0 and (stop - start)/step + */ + class UHD_API range_t{ + public: + + /*! + * Create a range from a single value. + * The step size will be taken as zero. + * \param value the only possible value in this range + */ + range_t(double value = 0); + + /*! + * Create a range from a full set of values. + * A step size of zero implies infinite precision. + * \param start the minimum value for this range + * \param stop the maximum value for this range + * \param step the step size for this range + */ + range_t(double start, double stop, double step = 0); + + //! Get the start value for this range. + double start(void) const; + + //! Get the stop value for this range. + double stop(void) const; + + //! Get the step value for this range. + double step(void) const; + + //! Convert this range to a printable string + const std::string to_pp_string(void) const; + + private: UHD_PIMPL_DECL(impl) _impl; + }; + + /*! + * A meta-range object holds a list of individual ranges. + */ + struct UHD_API meta_range_t : std::vector<range_t>{ + + //! A default constructor for an empty meta-range + meta_range_t(void); + + /*! + * Input iterator constructor: + * Makes boost::assign::list_of work. + * \param first the begin iterator + * \param last the end iterator + */ + template <typename InputIterator> + meta_range_t(InputIterator first, InputIterator last): + std::vector<range_t>(first, last){ /* NOP */ } + + /*! + * A convenience constructor for a single range. + * A step size of zero implies infinite precision. + * \param start the minimum value for this range + * \param stop the maximum value for this range + * \param step the step size for this range + */ + meta_range_t(double start, double stop, double step = 0); + + //! Get the overall start value for this meta-range. + double start(void) const; + + //! Get the overall stop value for this meta-range. + double stop(void) const; + + //! Get the overall step value for this meta-range. + double step(void) const; + + /*! + * Clip the target value to a possible range value. + * \param value the value to clip to this range + * \param clip_step if true, clip to steps as well + * \return a value that is in one of the ranges + */ + double clip(double value, bool clip_step = false) const; + + //! Convert this meta-range to a printable string + const std::string to_pp_string(void) const; + + }; + + typedef meta_range_t gain_range_t; + typedef meta_range_t freq_range_t; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_RANGES_HPP */ diff --git a/host/include/uhd/types/ref_vector.hpp b/host/include/uhd/types/ref_vector.hpp new file mode 100644 index 000000000..bbfb5434d --- /dev/null +++ b/host/include/uhd/types/ref_vector.hpp @@ -0,0 +1,85 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_REF_VECTOR_HPP +#define INCLUDED_UHD_TYPES_REF_VECTOR_HPP + +#include <uhd/config.hpp> + +namespace uhd{ + +/*! + * Reference vector: + * - Provides a std::vector-like interface for an array. + * - Statically sized, and does not manage the memory. + */ +template <typename T> class UHD_API ref_vector{ +public: + /*! + * Create a reference vector of size 1 from a pointer. + * Therefore: rv[0] == ptr and rv.size() == 1 + * \param ptr a pointer to a chunk of memory + */ + template <typename Ptr> ref_vector(Ptr *ptr): + _ptr(T(ptr)), _mem(_mem_t(&_ptr)), _size(1) + { + /* NOP */ + } + + /*! + * Create a reference vector from a std::vector container. + * Therefore: rv[n] == vec[n] and rv.size() == vec.size() + * \param vec a const reference to an std::vector + */ + template <typename Vector> ref_vector(const Vector &vec): + _ptr(T()), _mem(_mem_t(&vec.front())), _size(vec.size()) + { + /* NOP */ + } + + /*! + * Create a reference vector from a pointer and a length + * Therefore: rv[n] == mem[n] and rv.size() == len + * \param mem a pointer to an array of pointers + * \param len the length of the array of pointers + */ + ref_vector(const T *mem, size_t len): + _ptr(T()), _mem(_mem_t(mem)), _size(len) + { + /* NOP */ + } + + //! Index operator gets the value of rv[index] + const T &operator[](size_t index) const{ + return _mem[index]; + } + + //! The number of elements in this container + size_t size(void) const{ + return _size; + } + +private: + const T _ptr; + typedef T* _mem_t; + const _mem_t _mem; + const size_t _size; +}; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_REF_VECTOR_HPP */ diff --git a/host/include/uhd/types/sensors.hpp b/host/include/uhd/types/sensors.hpp new file mode 100644 index 000000000..529e1e3e3 --- /dev/null +++ b/host/include/uhd/types/sensors.hpp @@ -0,0 +1,137 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_SENSORS_HPP +#define INCLUDED_UHD_TYPES_SENSORS_HPP + +#include <uhd/config.hpp> +#include <string> + +namespace uhd{ + + /*! + * A sensor value stores a sensor reading as a string with unit and data type. + * The sensor value class can be used in the following way: + * + * sensor_value_t ref_lock_sensor("Reference", my_lock, "unlocked", "locked"); + * std::cout << ref_lock_sensor.to_pp_string() << std::endl; + * //prints Reference: locked + * + * sensor_value_t temp_sensor("Temperature", my_temp, "C"); + * std::cout << temp_sensor.to_pp_string() << std::endl; + * //prints Temperature: 38.5 C + */ + struct UHD_API sensor_value_t{ + + /*! + * Create a sensor value from a boolean. + * \param name the name of the sensor + * \param value the value true or false + * \param utrue the unit string when value is true + * \param ufalse the unit string when value is false + */ + sensor_value_t( + const std::string &name, + bool value, + const std::string &utrue, + const std::string &ufalse + ); + + /*! + * Create a sensor value from an integer. + * \param name the name of the sensor + * \param value the signed integer value + * \param unit the associated unit type + * \param formatter the formatter string + */ + sensor_value_t( + const std::string &name, + signed value, + const std::string &unit, + const std::string &formatter = "%d" + ); + + /*! + * Create a sensor value from a real number. + * \param name the name of the sensor + * \param value the real number value + * \param unit the associated unit type + * \param formatter the formatter string + */ + sensor_value_t( + const std::string &name, + double value, + const std::string &unit, + const std::string &formatter = "%f" + ); + + /*! + * Create a sensor value from a string. + * \param name the name of the sensor + * \param value the real number value + * \param unit the associated unit type + */ + sensor_value_t( + const std::string &name, + const std::string &value, + const std::string &unit + ); + + //! convert the sensor value to a boolean + bool to_bool(void) const; + + //! convert the sensor value to an integer + signed to_int(void) const; + + //! convert the sensor value to real number + double to_real(void) const; + + //! The name of the sensor value + const std::string name; + + /*! + * The sensor value as a string. + * For integer and real number types, this will be the output of the formatter. + * For boolean types, the value will be the string literal "true" or "false". + */ + const std::string value; + + /*! + * The sensor value's unit type. + * For boolean types, this will be the one of the two units + * depending upon the value of the boolean true or false. + */ + const std::string unit; + + //! Enumeration of possible data types in a sensor + enum data_type_t { + BOOLEAN = 'b', + INTEGER = 'i', + REALNUM = 'r', + STRING = 's' + }; + + //! The data type of the value + const data_type_t type; + + //! Convert this sensor value into a printable string + std::string to_pp_string(void) const; + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_SENSORS_HPP */ diff --git a/host/include/uhd/types/serial.hpp b/host/include/uhd/types/serial.hpp new file mode 100644 index 000000000..f66822775 --- /dev/null +++ b/host/include/uhd/types/serial.hpp @@ -0,0 +1,202 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_SERIAL_HPP +#define INCLUDED_UHD_TYPES_SERIAL_HPP + +#include <uhd/config.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/cstdint.hpp> +#include <vector> + +namespace uhd{ + + /*! + * Byte vector typedef for passing data in and out of I2C interfaces. + */ + typedef std::vector<boost::uint8_t> byte_vector_t; + + /*! + * The i2c interface class: + * Provides i2c and eeprom functionality. + * A subclass should only have to implement the i2c routines. + * An eeprom implementation comes for free with the interface. + * + * The eeprom routines are implemented on top of i2c. + * The built in eeprom implementation only does single + * byte reads and byte writes over the i2c interface, + * so it should be portable across multiple eeproms. + * Override the eeprom routines if this is not acceptable. + */ + class UHD_API i2c_iface{ + public: + typedef boost::shared_ptr<i2c_iface> sptr; + + /*! + * Write bytes over the i2c. + * \param addr the address + * \param buf the vector of bytes + */ + virtual void write_i2c( + boost::uint8_t addr, + const byte_vector_t &buf + ) = 0; + + /*! + * Read bytes over the i2c. + * \param addr the address + * \param num_bytes number of bytes to read + * \return a vector of bytes + */ + virtual byte_vector_t read_i2c( + boost::uint8_t addr, + size_t num_bytes + ) = 0; + + /*! + * Write bytes to an eeprom. + * \param addr the address + * \param offset byte offset + * \param buf the vector of bytes + */ + virtual void write_eeprom( + boost::uint8_t addr, + boost::uint8_t offset, + const byte_vector_t &buf + ); + + /*! + * Read bytes from an eeprom. + * \param addr the address + * \param offset byte offset + * \param num_bytes number of bytes to read + * \return a vector of bytes + */ + virtual byte_vector_t read_eeprom( + boost::uint8_t addr, + boost::uint8_t offset, + size_t num_bytes + ); + }; + + /*! + * The SPI configuration struct: + * Used to configure a SPI transaction interface. + */ + struct UHD_API spi_config_t{ + /*! + * The edge type specifies when data is valid + * relative to the edge of the serial clock. + */ + enum edge_t{ + EDGE_RISE = 'r', + EDGE_FALL = 'f' + }; + + //! on what edge is the mosi data valid? + edge_t mosi_edge; + + //! on what edge is the miso data valid? + edge_t miso_edge; + + /*! + * Create a new spi config. + * \param edge the default edge for mosi and miso + */ + spi_config_t(edge_t edge = EDGE_RISE); + }; + + /*! + * The SPI interface class. + * Provides routines to transact SPI and do other useful things which haven't been defined yet. + */ + class UHD_API spi_iface{ + public: + typedef boost::shared_ptr<spi_iface> sptr; + + /*! + * Perform a spi transaction. + * \param which_slave the slave device number + * \param config spi config args + * \param data the bits to write + * \param num_bits how many bits in data + * \param readback true to readback a value + * \return spi data if readback set + */ + virtual boost::uint32_t transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback + ) = 0; + + /*! + * Read from the SPI bus. + * \param which_slave the slave device number + * \param config spi config args + * \param data the bits to write out (be sure to set write bit) + * \param num_bits how many bits in data + * \return spi data + */ + virtual boost::uint32_t read_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + /*! + * Write to the SPI bus. + * \param which_slave the slave device number + * \param config spi config args + * \param data the bits to write + * \param num_bits how many bits in data + */ + virtual void write_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + }; + + /*! + * UART interface to write and read bytes. + */ + class UHD_API uart_iface{ + public: + typedef boost::shared_ptr<uart_iface> sptr; + + /*! + * Write to a serial port. + * \param buf the data to write + */ + virtual void write_uart(const std::string &buf) = 0; + + /*! + * Read from a serial port. + * Reads until complete line or timeout. + * \param timeout the timeout in seconds + * \return the data read from the serial port + */ + virtual std::string read_uart(double timeout) = 0; + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_SERIAL_HPP */ diff --git a/host/include/uhd/types/stream_cmd.hpp b/host/include/uhd/types/stream_cmd.hpp new file mode 100644 index 000000000..41708e2e2 --- /dev/null +++ b/host/include/uhd/types/stream_cmd.hpp @@ -0,0 +1,64 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_STREAM_CMD_HPP +#define INCLUDED_UHD_TYPES_STREAM_CMD_HPP + +#include <uhd/config.hpp> +#include <uhd/types/time_spec.hpp> + +namespace uhd{ + + /*! + * Command struct for configuration and control of streaming: + * + * A stream command defines how the device sends samples to the host. + * Streaming is controlled by submitting a stream command to the rx dsp. + * Granular control over what the device streams to the host can be + * achieved through submission of multiple (carefully-crafted) commands. + * + * The mode parameter controls how streaming is issued to the device: + * - "Start continuous" tells the device to stream samples indefinitely. + * - "Stop continuous" tells the device to end continuous streaming. + * - "Num samps and done" tells the device to stream num samps and + * to not expect a future stream command for contiguous samples. + * - "Num samps and more" tells the device to stream num samps and + * to expect a future stream command for contiguous samples. + * + * The stream now parameter controls when the stream begins. + * When true, the device will begin streaming ASAP. When false, + * the device will begin streaming at a time specified by time_spec. + */ + struct UHD_API stream_cmd_t{ + + enum stream_mode_t { + STREAM_MODE_START_CONTINUOUS = 'a', + STREAM_MODE_STOP_CONTINUOUS = 'o', + STREAM_MODE_NUM_SAMPS_AND_DONE = 'd', + STREAM_MODE_NUM_SAMPS_AND_MORE = 'm' + } stream_mode; + size_t num_samps; + + bool stream_now; + time_spec_t time_spec; + + stream_cmd_t(const stream_mode_t &stream_mode); + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_STREAM_CMD_HPP */ diff --git a/host/include/uhd/types/time_spec.hpp b/host/include/uhd/types/time_spec.hpp new file mode 100644 index 000000000..e7d7d5ab4 --- /dev/null +++ b/host/include/uhd/types/time_spec.hpp @@ -0,0 +1,141 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_TIME_SPEC_HPP +#define INCLUDED_UHD_TYPES_TIME_SPEC_HPP + +#include <uhd/config.hpp> +#include <boost/operators.hpp> +#include <ctime> + +namespace uhd{ + + /*! + * A time_spec_t holds a seconds and a fractional seconds time value. + * Depending upon usage, the time_spec_t can represent absolute times, + * relative times, or time differences (between absolute times). + * + * The time_spec_t provides clock-domain independent time storage, + * but can convert fractional seconds to/from clock-domain specific units. + * + * The fractional seconds are stored as double precision floating point. + * This gives the fractional seconds enough precision to unambiguously + * specify a clock-tick/sample-count up to rates of several petahertz. + */ + class UHD_API time_spec_t : boost::additive<time_spec_t>, boost::totally_ordered<time_spec_t>{ + public: + + /*! + * Get the system time in time_spec_t format. + * Uses the highest precision clock available. + * \return the system time as a time_spec_t + */ + static time_spec_t get_system_time(void); + + /*! + * Create a time_spec_t from a real-valued seconds count. + * \param secs the real-valued seconds count (default = 0) + */ + time_spec_t(double secs = 0); + + /*! + * Create a time_spec_t from whole and fractional seconds. + * \param full_secs the whole/integer seconds count + * \param frac_secs the fractional seconds count (default = 0) + */ + time_spec_t(time_t full_secs, double frac_secs = 0); + + /*! + * Create a time_spec_t from whole seconds and fractional ticks. + * Translation from clock-domain specific units. + * \param full_secs the whole/integer seconds count + * \param tick_count the fractional seconds tick count + * \param tick_rate the number of ticks per second + */ + time_spec_t(time_t full_secs, long tick_count, double tick_rate); + + /*! + * Create a time_spec_t from a 64-bit tick count. + * Translation from clock-domain specific units. + * \param ticks an integer count of ticks + * \param tick_rate the number of ticks per second + */ + static time_spec_t from_ticks(long long ticks, double tick_rate); + + /*! + * Convert the fractional seconds to clock ticks. + * Translation into clock-domain specific units. + * \param tick_rate the number of ticks per second + * \return the fractional seconds tick count + */ + long get_tick_count(double tick_rate) const; + + /*! + * Convert the time spec into a 64-bit tick count. + * Translation into clock-domain specific units. + * \param tick_rate the number of ticks per second + * \return an integer number of ticks + */ + long long to_ticks(const double tick_rate) const; + + /*! + * Get the time as a real-valued seconds count. + * Note: If this time_spec_t represents an absolute time, + * the precision of the fractional seconds may be lost. + * \return the real-valued seconds + */ + double get_real_secs(void) const; + + /*! + * Get the whole/integer part of the time in seconds. + * \return the whole/integer seconds + */ + time_t get_full_secs(void) const; + + /*! + * Get the fractional part of the time in seconds. + * \return the fractional seconds + */ + double get_frac_secs(void) const; + + //! Implement addable interface + time_spec_t &operator+=(const time_spec_t &); + + //! Implement subtractable interface + time_spec_t &operator-=(const time_spec_t &); + + //private time storage details + private: time_t _full_secs; double _frac_secs; + }; + + //! Implement equality_comparable interface + UHD_API bool operator==(const time_spec_t &, const time_spec_t &); + + //! Implement less_than_comparable interface + UHD_API bool operator<(const time_spec_t &, const time_spec_t &); + + UHD_INLINE time_t time_spec_t::get_full_secs(void) const{ + return this->_full_secs; + } + + UHD_INLINE double time_spec_t::get_frac_secs(void) const{ + return this->_frac_secs; + } + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_TIME_SPEC_HPP */ diff --git a/host/include/uhd/types/tune_request.hpp b/host/include/uhd/types/tune_request.hpp new file mode 100644 index 000000000..9c498bfe9 --- /dev/null +++ b/host/include/uhd/types/tune_request.hpp @@ -0,0 +1,95 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_TUNE_REQUEST_HPP +#define INCLUDED_UHD_TYPES_TUNE_REQUEST_HPP + +#include <uhd/config.hpp> + +namespace uhd{ + + /*! + * A tune request instructs the implementation how to tune the RF chain. + * The policies can be used to select automatic tuning or + * fined control over the daughterboard IF and DSP tuning. + * Not all combinations of policies are applicable. + * Convenience constructors are supplied for most use cases. + */ + struct UHD_API tune_request_t{ + /*! + * Make a new tune request for a particular center frequency. + * Use an automatic policy for the RF and DSP frequency + * to tune the chain as close as possible to the target frequency. + * \param target_freq the target frequency in Hz + */ + tune_request_t(double target_freq = 0); + + /*! + * Make a new tune request for a particular center frequency. + * Use a manual policy for the RF frequency, + * and an automatic policy for the DSP frequency, + * to tune the chain as close as possible to the target frequency. + * \param target_freq the target frequency in Hz + * \param lo_off the LO offset frequency in Hz + */ + tune_request_t(double target_freq, double lo_off); + + //! Policy options for tunable elements in the RF chain. + enum policy_t { + //! Do not set this argument, use current setting. + POLICY_NONE = int('N'), + //! Automatically determine the argument's value. + POLICY_AUTO = int('A'), + //! Use the argument's value for the setting. + POLICY_MANUAL = int('M') + }; + + /*! + * The target frequency of the overall chain in Hz. + * Set this even if all policies are set to manual. + */ + double target_freq; + + /*! + * The policy for the RF frequency. + * Automatic behavior: the target frequency + default LO offset. + */ + policy_t rf_freq_policy; + + /*! + * The RF frequency in Hz. + * Set when the policy is set to manual. + */ + double rf_freq; + + /*! + * The policy for the DSP frequency. + * Automatic behavior: the difference between the target and IF. + */ + policy_t dsp_freq_policy; + + /*! + * The DSP frequency in Hz. + * Set when the policy is set to manual. + */ + double dsp_freq; + + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_TUNE_REQUEST_HPP */ diff --git a/host/include/uhd/types/tune_result.hpp b/host/include/uhd/types/tune_result.hpp new file mode 100644 index 000000000..e51473085 --- /dev/null +++ b/host/include/uhd/types/tune_result.hpp @@ -0,0 +1,44 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TYPES_TUNE_RESULT_HPP +#define INCLUDED_UHD_TYPES_TUNE_RESULT_HPP + +#include <uhd/config.hpp> +#include <string> + +namespace uhd{ + + /*! + * The tune result struct holds result of a 2-phase tuning. + */ + struct UHD_API tune_result_t{ + double target_rf_freq; + double actual_rf_freq; + double target_dsp_freq; + double actual_dsp_freq; + + /*! + * Create a pretty print string for this tune result struct. + * \return the printable string + */ + std::string to_pp_string(void) const; + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_TYPES_TUNE_RESULT_HPP */ diff --git a/host/include/uhd/usrp/CMakeLists.txt b/host/include/uhd/usrp/CMakeLists.txt new file mode 100644 index 000000000..d7b936fc2 --- /dev/null +++ b/host/include/uhd/usrp/CMakeLists.txt @@ -0,0 +1,38 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + + +INSTALL(FILES + + #### dboard headers ### + dboard_base.hpp + dboard_eeprom.hpp + dboard_id.hpp + dboard_iface.hpp + dboard_manager.hpp + + ### utilities ### + gps_ctrl.hpp + mboard_eeprom.hpp + subdev_spec.hpp + + ### interfaces ### + multi_usrp.hpp + + DESTINATION ${INCLUDE_DIR}/uhd/usrp + COMPONENT headers +) diff --git a/host/include/uhd/usrp/dboard_base.hpp b/host/include/uhd/usrp/dboard_base.hpp new file mode 100644 index 000000000..31b3643c7 --- /dev/null +++ b/host/include/uhd/usrp/dboard_base.hpp @@ -0,0 +1,98 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_DBOARD_BASE_HPP +#define INCLUDED_UHD_USRP_DBOARD_BASE_HPP + +#include <uhd/config.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/pimpl.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/dboard_iface.hpp> + +namespace uhd{ namespace usrp{ + +/*! + * A daughter board dboard_base class for all dboards. + * Only other dboard dboard_base classes should inherit this. + */ +class UHD_API dboard_base : boost::noncopyable{ +public: + typedef boost::shared_ptr<dboard_base> sptr; + /*! + * An opaque type for the dboard constructor args. + * Derived classes should pass the args into the base class, + * but should not deal with the internals of the args. + */ + typedef void * ctor_args_t; + + //structors + dboard_base(ctor_args_t); + +protected: + std::string get_subdev_name(void); + dboard_iface::sptr get_iface(void); + dboard_id_t get_rx_id(void); + dboard_id_t get_tx_id(void); + property_tree::sptr get_rx_subtree(void); + property_tree::sptr get_tx_subtree(void); + +private: + UHD_PIMPL_DECL(impl) _impl; +}; + +/*! + * A xcvr daughter board implements rx and tx methods + * Sub classes for xcvr boards should inherit this. + */ +class UHD_API xcvr_dboard_base : public dboard_base{ +public: + /*! + * Create a new xcvr dboard object, override in subclasses. + */ + xcvr_dboard_base(ctor_args_t); +}; + +/*! + * A rx daughter board only implements rx methods. + * Sub classes for rx-only boards should inherit this. + */ +class UHD_API rx_dboard_base : public dboard_base{ +public: + /*! + * Create a new rx dboard object, override in subclasses. + */ + rx_dboard_base(ctor_args_t); +}; + +/*! + * A tx daughter board only implements tx methods. + * Sub classes for rx-only boards should inherit this. + */ +class UHD_API tx_dboard_base : public dboard_base{ +public: + /*! + * Create a new rx dboard object, override in subclasses. + */ + tx_dboard_base(ctor_args_t); +}; + +}} //namespace + +#endif /* INCLUDED_UHD_USRP_DBOARD_BASE_HPP */ diff --git a/host/include/uhd/usrp/dboard_eeprom.hpp b/host/include/uhd/usrp/dboard_eeprom.hpp new file mode 100644 index 000000000..93fc5ac7d --- /dev/null +++ b/host/include/uhd/usrp/dboard_eeprom.hpp @@ -0,0 +1,62 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_DBOARD_EEPROM_HPP +#define INCLUDED_UHD_USRP_DBOARD_EEPROM_HPP + +#include <uhd/config.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/types/serial.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + +struct UHD_API dboard_eeprom_t{ + + //! The ID for the daughterboard type + dboard_id_t id; + + //! The unique serial number + std::string serial; + + //! A hardware revision number + std::string revision; + + /*! + * Create an empty dboard eeprom struct. + */ + dboard_eeprom_t(void); + + /*! + * Load the object with bytes from the eeprom. + * \param iface the serial interface with i2c + * \param addr the i2c address for the eeprom + */ + void load(i2c_iface &iface, boost::uint8_t addr); + + /*! + * Store the object to bytes in the eeprom. + * \param iface the serial interface with i2c + * \param addr the i2c address for the eeprom + */ + void store(i2c_iface &iface, boost::uint8_t addr) const; + +}; + +}} //namespace + +#endif /* INCLUDED_UHD_USRP_DBOARD_EEPROM_HPP */ diff --git a/host/include/uhd/usrp/dboard_id.hpp b/host/include/uhd/usrp/dboard_id.hpp new file mode 100644 index 000000000..1fda8182e --- /dev/null +++ b/host/include/uhd/usrp/dboard_id.hpp @@ -0,0 +1,96 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_DBOARD_ID_HPP +#define INCLUDED_UHD_USRP_DBOARD_ID_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <boost/operators.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + + class UHD_API dboard_id_t : boost::equality_comparable<dboard_id_t>{ + public: + /*! + * Create a dboard id from an integer. + * \param id the integer representation + */ + dboard_id_t(boost::uint16_t id = 0xffff); + + /*! + * Obtain a dboard id that represents no dboard. + * \return the dboard id with the 0xffff id. + */ + static dboard_id_t none(void); + + /*! + * Create a new dboard id from an integer representation. + * \param uint16 an unsigned 16 bit integer + * \return a new dboard id containing the integer + */ + static dboard_id_t from_uint16(boost::uint16_t uint16); + + /*! + * Get the dboard id represented as an integer. + * \return an unsigned 16 bit integer representation + */ + boost::uint16_t to_uint16(void) const; + + /*! + * Create a new dboard id from a string representation. + * If the string has a 0x prefix, it will be parsed as hex. + * \param string a numeric string, possibly hex + * \return a new dboard id containing the integer + */ + static dboard_id_t from_string(const std::string &string); + + /*! + * Get the dboard id represented as an integer. + * \return a hex string representation with 0x prefix + */ + std::string to_string(void) const; + + /*! + * Get the dboard id represented as a canonical name. + * \return the canonical string representation + */ + std::string to_cname(void) const; + + /*! + * Get the pretty print representation of this dboard id. + * \return a string with the dboard name and id number + */ + std::string to_pp_string(void) const; + + private: + boost::uint16_t _id; //internal representation + }; + + /*! + * Comparator operator overloaded for dboard ids. + * The boost::equality_comparable provides the !=. + * \param lhs the dboard id to the left of the operator + * \param rhs the dboard id to the right of the operator + * \return true when the dboard ids are equal + */ + UHD_API bool operator==(const dboard_id_t &lhs, const dboard_id_t &rhs); + +}} //namespace + +#endif /* INCLUDED_UHD_USRP_DBOARD_ID_HPP */ diff --git a/host/include/uhd/usrp/dboard_iface.hpp b/host/include/uhd/usrp/dboard_iface.hpp new file mode 100644 index 000000000..ca5de5c2f --- /dev/null +++ b/host/include/uhd/usrp/dboard_iface.hpp @@ -0,0 +1,298 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_DBOARD_IFACE_HPP +#define INCLUDED_UHD_USRP_DBOARD_IFACE_HPP + +#include <uhd/config.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/cstdint.hpp> +#include <string> +#include <vector> + +namespace uhd{ namespace usrp{ + +//! Special properties that differentiate this daughterboard slot +struct UHD_API dboard_iface_special_props_t{ + /*! + * Soft clock divider: + * When a motherboard cannot provided a divided dboard clock, + * it may provided a "soft" divided clock over an FPGA GPIO. + * The implementation must know the type of clock provided. + */ + bool soft_clock_divider; + + /*! + * Mangle i2c addresses: + * When i2c is shared across multiple daugterboard slots, + * the i2c addresses will be mangled on the secondary slot + * to avoid conflicts between slots in the i2c address space. + * The mangling is daguhterboard specific so the implementation + * needs to know whether it should use mangled addresses or not. + */ + bool mangle_i2c_addrs; +}; + +/*! + * The daughter board dboard interface to be subclassed. + * A dboard instance interfaces with the mboard though this api. + * This interface provides i2c, spi, gpio, atr, aux dac/adc access. + * Each mboard should have a specially tailored iface for its dboard. + */ +class UHD_API dboard_iface : public uhd::i2c_iface{ +public: + typedef boost::shared_ptr<dboard_iface> sptr; + typedef dboard_iface_special_props_t special_props_t; + + //! tells the host which unit to use + enum unit_t{ + UNIT_RX = int('r'), + UNIT_TX = int('t') + }; + + //! possible atr registers + enum atr_reg_t{ + ATR_REG_IDLE = int('i'), + ATR_REG_TX_ONLY = int('t'), + ATR_REG_RX_ONLY = int('r'), + ATR_REG_FULL_DUPLEX = int('f') + }; + + //! aux dac selection enums (per unit) + enum aux_dac_t{ + AUX_DAC_A = int('a'), + AUX_DAC_B = int('b'), + AUX_DAC_C = int('c'), + AUX_DAC_D = int('d') + }; + + //! aux adc selection enums (per unit) + enum aux_adc_t{ + AUX_ADC_A = int('a'), + AUX_ADC_B = int('b') + }; + + /*! + * Get special properties information for this dboard slot. + * This call helps the dboard code to handle implementation + * differences between different motherboards and dboard slots. + * \return the special properties struct + */ + virtual special_props_t get_special_props(void) = 0; + + /*! + * Write to an aux dac. + * + * \param unit which unit rx or tx + * \param which_dac the dac index 0, 1, 2, 3... + * \param value the value in volts + */ + virtual void write_aux_dac(unit_t unit, aux_dac_t which_dac, double value) = 0; + + /*! + * Read from an aux adc. + * + * \param unit which unit rx or tx + * \param which_adc the adc index 0, 1, 2, 3... + * \return the value in volts + */ + virtual double read_aux_adc(unit_t unit, aux_adc_t which_adc) = 0; + + /*! + * Set a daughterboard output pin control source. + * + * \param unit which unit rx or tx + * \param value 16-bits, 0=GPIO controlled, 1=ATR controlled + * \param mask 16-bits, 0=do not change, 1=change value + */ + virtual void set_pin_ctrl( + unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff + ); + + /*! + * Read back the pin control setting. + * + * \param unit which unit rx or tx + * \return the 16-bit settings value + */ + virtual boost::uint16_t get_pin_ctrl(unit_t unit); + + /*! + * Set a daughterboard ATR register. + * + * \param unit which unit rx or tx + * \param reg which ATR register + * \param value 16-bits, 0=ATR output low, 1=ATR output high + * \param mask 16-bits, 0=do not change, 1=change value + */ + virtual void set_atr_reg( + unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask = 0xffff + ); + + /*! + * Read back an ATR register setting. + * + * \param unit which unit rx or tx + * \param reg which ATR register + * \return the 16-bit settings value + */ + virtual boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg); + + /*! + * Set daughterboard GPIO data direction setting. + * + * \param unit which unit rx or tx + * \param value 16-bits, 0=GPIO input, 1=GPIO output + * \param mask 16-bits, 0=do not change, 1=change value + */ + virtual void set_gpio_ddr( + unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff + ); + + /*! + * Read back the GPIO data direction setting. + * + * \param unit which unit rx or tx + * \return the 16-bit settings value + */ + virtual boost::uint16_t get_gpio_ddr(unit_t unit); + + /*! + * Set daughterboard GPIO pin output setting. + * + * \param unit which unit rx or tx + * \param value 16-bits, 0=GPIO output low, 1=GPIO output high + * \param mask 16-bits, 0=do not change, 1=change value + */ + virtual void set_gpio_out( + unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff + ); + + /*! + * Read back the GPIO pin output setting. + * + * \param unit which unit rx or tx + * \return the 16-bit settings value + */ + virtual boost::uint16_t get_gpio_out(unit_t unit); + + /*! + * Setup the GPIO debug mux. + * + * \param unit which unit rx or tx + * \param which which debug: 0, 1 + */ + virtual void set_gpio_debug(unit_t unit, int which) = 0; + + /*! + * Read daughterboard GPIO pin values. + * + * \param unit which unit rx or tx + * \return the value of the gpio unit + */ + virtual boost::uint16_t read_gpio(unit_t unit) = 0; + + /*! + * Write data to SPI bus peripheral. + * + * \param unit which unit, rx or tx + * \param config configuration settings + * \param data the bits to write LSB first + * \param num_bits the number of bits in data + */ + virtual void write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ) = 0; + + /*! + * Read and write data to SPI bus peripheral. + * + * \param unit which unit, rx or tx + * \param config configuration settings + * \param data the bits to write LSB first + * \param num_bits the number of bits in data + * \return the data that was read + */ + virtual boost::uint32_t read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ) = 0; + + /*! + * Set the rate of a dboard clock. + * + * \param unit which unit rx or tx + * \param rate the clock rate in Hz + */ + virtual void set_clock_rate(unit_t unit, double rate) = 0; + + /*! + * Get the rate of a dboard clock. + * + * \param unit which unit rx or tx + * \return the clock rate in Hz + */ + virtual double get_clock_rate(unit_t unit) = 0; + + /*! + * Get a list of possible rates for the dboard clock. + * + * \param unit which unit rx or tx + * \return a list of clock rates in Hz + */ + virtual std::vector<double> get_clock_rates(unit_t unit) = 0; + + /*! + * Enable or disable a dboard clock. + * + * \param unit which unit rx or tx + * \param enb true for enabled + */ + virtual void set_clock_enabled(unit_t unit, bool enb) = 0; + + /*! + * Get the rate of the codec. + * For rx, this is the rate the ADC feeds the DSP. + * For tx, this is the rate the DSP feeds the DAC. + * \param unit which unit rx or tx + * \return the codec rate in Hz + */ + virtual double get_codec_rate(unit_t unit) = 0; + +private: + UHD_PIMPL_DECL(impl) _impl; + + virtual void _set_pin_ctrl(unit_t unit, boost::uint16_t value) = 0; + virtual void _set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint16_t value) = 0; + virtual void _set_gpio_ddr(unit_t unit, boost::uint16_t value) = 0; + virtual void _set_gpio_out(unit_t unit, boost::uint16_t value) = 0; + +protected: + dboard_iface(void); + +}; + +}} //namespace + +#endif /* INCLUDED_UHD_USRP_DBOARD_IFACE_HPP */ diff --git a/host/include/uhd/usrp/dboard_manager.hpp b/host/include/uhd/usrp/dboard_manager.hpp new file mode 100644 index 000000000..d3a3ffb5c --- /dev/null +++ b/host/include/uhd/usrp/dboard_manager.hpp @@ -0,0 +1,96 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_DBOARD_MANAGER_HPP +#define INCLUDED_UHD_USRP_DBOARD_MANAGER_HPP + +#include <uhd/config.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <string> +#include <vector> + +namespace uhd{ namespace usrp{ + +/*! + * A daughter board subdev dboard_manager class. + * Create subdev instances for each subdev on a dboard. + * Provide wax::obj access to the subdevs inside. + */ +class UHD_API dboard_manager : boost::noncopyable{ +public: + typedef boost::shared_ptr<dboard_manager> sptr; + + //dboard constructor (each dboard should have a ::make with this signature) + typedef dboard_base::sptr(*dboard_ctor_t)(dboard_base::ctor_args_t); + + /*! + * Register a rx or tx dboard into the system. + * For single subdevice boards, omit subdev_names. + * \param dboard_id the dboard id (rx or tx) + * \param dboard_ctor the dboard constructor function pointer + * \param name the canonical name for the dboard represented + * \param subdev_names the names of the subdevs on this dboard + */ + static void register_dboard( + const dboard_id_t &dboard_id, + dboard_ctor_t dboard_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0") + ); + + /*! + * Register an xcvr dboard into the system. + * For single subdevice boards, omit subdev_names. + * \param rx_dboard_id the rx unit dboard id + * \param tx_dboard_id the tx unit dboard id + * \param dboard_ctor the dboard constructor function pointer + * \param name the canonical name for the dboard represented + * \param subdev_names the names of the subdevs on this dboard + */ + static void register_dboard( + const dboard_id_t &rx_dboard_id, + const dboard_id_t &tx_dboard_id, + dboard_ctor_t dboard_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0") + ); + + /*! + * Make a new dboard manager. + * \param rx_dboard_id the id of the rx dboard + * \param tx_dboard_id the id of the tx dboard + * \param gdboard_id the id of the grand-dboard + * \param iface the custom dboard interface + * \param subtree the subtree to load with props + * \return an sptr to the new dboard manager + */ + static sptr make( + dboard_id_t rx_dboard_id, + dboard_id_t tx_dboard_id, + dboard_id_t gdboard_id, + dboard_iface::sptr iface, + property_tree::sptr subtree + ); +}; + +}} //namespace + +#endif /* INCLUDED_UHD_USRP_DBOARD_MANAGER_HPP */ diff --git a/host/include/uhd/usrp/gps_ctrl.hpp b/host/include/uhd/usrp/gps_ctrl.hpp new file mode 100644 index 000000000..0c1389d48 --- /dev/null +++ b/host/include/uhd/usrp/gps_ctrl.hpp @@ -0,0 +1,61 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_GPS_CTRL_HPP +#define INCLUDED_GPS_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/function.hpp> +#include <vector> + +namespace uhd{ + +class UHD_API gps_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<gps_ctrl> sptr; + + /*! + * Make a GPS config for Jackson Labs or generic NMEA GPS devices + */ + static sptr make(uart_iface::sptr uart); + + /*! + * Retrieve the list of sensors this GPS object provides + */ + virtual std::vector<std::string> get_sensors(void) = 0; + + /*! + * Retrieve the named sensor + */ + virtual uhd::sensor_value_t get_sensor(std::string key) = 0; + + /*! + * Tell you if there's a supported GPS connected or not + * \return true if a supported GPS is connected + */ + virtual bool gps_detected(void) = 0; + + //TODO: other fun things you can do with a GPS. + +}; + +} //namespace uhd + +#endif /* INCLUDED_GPS_CTRL_HPP */ diff --git a/host/include/uhd/usrp/mboard_eeprom.hpp b/host/include/uhd/usrp/mboard_eeprom.hpp new file mode 100644 index 000000000..c47f894be --- /dev/null +++ b/host/include/uhd/usrp/mboard_eeprom.hpp @@ -0,0 +1,66 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_MBOARD_EEPROM_HPP +#define INCLUDED_UHD_USRP_MBOARD_EEPROM_HPP + +#include <uhd/config.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/serial.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + + /*! + * The motherboard EEPROM object: + * Knows how to read and write the EEPROM for various USRPs. + * The class inherits from a string, string dictionary. + * Use the dictionary interface to get and set values. + * Commit to the EEPROM to save changed settings. + */ + struct UHD_API mboard_eeprom_t : uhd::dict<std::string, std::string>{ + + //! Possible EEPROM maps types + enum map_type{ + MAP_N100, + MAP_B000, + MAP_B100, + MAP_E100 + }; + + //! Make a new empty mboard eeprom + mboard_eeprom_t(void); + + /*! + * Make a new mboard EEPROM handler. + * \param iface the interface to i2c + * \param map the map type enum + */ + mboard_eeprom_t(i2c_iface &iface, map_type map); + + /*! + * Write the contents of this object to the EEPROM. + * \param iface the interface to i2c + * \param map the map type enum + */ + void commit(i2c_iface &iface, map_type map) const; + + }; + +}} //namespace + +#endif /* INCLUDED_UHD_USRP_MBOARD_EEPROM_HPP */ diff --git a/host/include/uhd/usrp/multi_usrp.hpp b/host/include/uhd/usrp/multi_usrp.hpp new file mode 100644 index 000000000..88affa40c --- /dev/null +++ b/host/include/uhd/usrp/multi_usrp.hpp @@ -0,0 +1,822 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_MULTI_USRP_HPP +#define INCLUDED_UHD_USRP_MULTI_USRP_HPP + +//define API capabilities for compile time detection of new features +#define UHD_USRP_MULTI_USRP_REF_SOURCES_API +#define UHD_USRP_MULTI_USRP_GET_RATES_API +#define UHD_USRP_MULTI_USRP_FRONTEND_CAL_API +#define UHD_USRP_MULTI_USRP_COMMAND_TIME_API +#define UHD_USRP_MULTI_USRP_BW_RANGE_API +#define UHD_USRP_MULTI_USRP_USER_REGS_API + +#include <uhd/config.hpp> +#include <uhd/device.hpp> +#include <uhd/deprecated.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/tune_request.hpp> +#include <uhd/types/tune_result.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <complex> +#include <string> +#include <vector> + +namespace uhd{ namespace usrp{ + +/*! + * The Multi-USRP device class: + * + * This class facilitates ease-of-use for most use-case scenarios. + * The wrapper provides convenience functions to tune the devices, + * set the dboard gains, antennas, filters, and other properties. + * This class can be used to interface with a single USRP with + * one or more channels, or multiple USRPs in a homogeneous setup. + * All members take an optional parameter for board number or channel number. + * In the single device, single channel case, these parameters can be unspecified. + * + * When using a single device with multiple channels: + * - Channel mapping is determined by the frontend specifications + * - All channels share a common RX sample rate + * - All channels share a common TX sample rate + * + * When using multiple devices in a configuration: + * - Channel mapping is determined by the device address arguments + * - All boards share a common RX sample rate + * - All boards share a common TX sample rate + * - All boards share a common RX frontend specification size + * - All boards share a common TX frontend specification size + * - All boards must have synchronized times (see the set_time_*() calls) + * + * Example to setup channel mapping for multiple devices: + * <pre> + * + * //create a multi_usrp with two boards in the configuration + * device_addr_t dev_addr; + * dev_addr["addr0"] = "192.168.10.2" + * dev_addr["addr1"] = "192.168.10.3"; + * multi_usrp::sptr dev = multi_usrp::make(dev_addr); + * + * //set the board on 10.2 to use the A RX frontend (RX channel 0) + * dev->set_rx_subdev_spec("A:A", 0); + * + * //set the board on 10.3 to use the B RX frontend (RX channel 1) + * dev->set_rx_subdev_spec("A:B", 1); + * + * //set both boards to use the AB TX frontend (TX channels 0 and 1) + * dev->set_tx_subdev_spec("A:AB", multi_usrp::ALL_MBOARDS); + * + * //now that all the channels are mapped, continue with configuration... + * + * </pre> + */ +class UHD_API multi_usrp : boost::noncopyable{ +public: + typedef boost::shared_ptr<multi_usrp> sptr; + + //! A wildcard motherboard index + static const size_t ALL_MBOARDS = size_t(~0); + + //! A wildcard channel index + static const size_t ALL_CHANS = size_t(~0); + + //! A wildcard gain element name + static const std::string ALL_GAINS; + + /*! + * Make a new multi usrp from the device address. + * \param dev_addr the device address + * \return a new single usrp object + */ + static sptr make(const device_addr_t &dev_addr); + + /*! + * Get the underlying device object. + * This is needed to get access to the streaming API and properties. + * \return the device object within this single usrp + */ + virtual device::sptr get_device(void) = 0; + + //! Convenience method to get a RX streamer + rx_streamer::sptr get_rx_stream(const stream_args_t &args){ + return this->get_device()->get_rx_stream(args); + } + + //! Convenience method to get a TX streamer + tx_streamer::sptr get_tx_stream(const stream_args_t &args){ + return this->get_device()->get_tx_stream(args); + } + + /******************************************************************* + * Mboard methods + ******************************************************************/ + + /*! + * Set the master clock rate. + * This controls the rate of the clock that feeds the FPGA DSP. + * On some devices, this re-tunes the clock to the specified rate. + * If the specified rate is not available, this method will throw. + * On other devices, this method notifies the software of the rate, + * but requires the the user has made the necessary hardware change. + * \param rate the new master clock rate in Hz + * \param mboard the motherboard index 0 to M-1 + */ + virtual void set_master_clock_rate(double rate, size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Get the master clock rate. + * \param mboard the motherboard index 0 to M-1 + * \return the master clock rate in Hz. + */ + virtual double get_master_clock_rate(size_t mboard = 0) = 0; + + /*! + * Get a printable summary for this USRP configuration. + * \return a printable string + */ + virtual std::string get_pp_string(void) = 0; + + /*! + * Get canonical name for this USRP motherboard. + * \param mboard which motherboard to query + * \return a string representing the name + */ + virtual std::string get_mboard_name(size_t mboard = 0) = 0; + + /*! + * Get the current time in the usrp time registers. + * \param mboard which motherboard to query + * \return a timespec representing current usrp time + */ + virtual time_spec_t get_time_now(size_t mboard = 0) = 0; + + /*! + * Get the time when the last pps pulse occured. + * \param mboard which motherboard to query + * \return a timespec representing the last pps + */ + virtual time_spec_t get_time_last_pps(size_t mboard = 0) = 0; + + /*! + * Sets the time registers on the usrp immediately. + * + * If only one MIMO master is present in your configuration, set_time_now is + * safe to use because the slave's time automatically follows the master's time. + * Otherwise, this call cannot set the time synchronously across multiple devices. + * Please use the set_time_next_pps or set_time_unknown_pps calls with a PPS signal. + * + * \param time_spec the time to latch into the usrp device + * \param mboard the motherboard index 0 to M-1 + */ + virtual void set_time_now(const time_spec_t &time_spec, size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Set the time registers on the usrp at the next pps tick. + * The values will not be latched in until the pulse occurs. + * It is recommended that the user sleep(1) after calling to ensure + * that the time registers will be in a known state prior to use. + * + * Note: Because this call sets the time on the "next" pps, + * the seconds in the time spec should be current seconds + 1. + * + * \param time_spec the time to latch into the usrp device + * \param mboard the motherboard index 0 to M-1 + */ + virtual void set_time_next_pps(const time_spec_t &time_spec, size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Synchronize the times across all motherboards in this configuration. + * Use this method to sync the times when the edge of the PPS is unknown. + * + * Ex: Host machine is not attached to serial port of GPSDO + * and can therefore not query the GPSDO for the PPS edge. + * + * This is a 2-step process, and will take at most 2 seconds to complete. + * Upon completion, the times will be synchronized to the time provided. + * + * - Step1: wait for the last pps time to transition to catch the edge + * - Step2: set the time at the next pps (synchronous for all boards) + * + * \param time_spec the time to latch at the next pps after catching the edge + */ + virtual void set_time_unknown_pps(const time_spec_t &time_spec) = 0; + + /*! + * Are the times across all motherboards in this configuration synchronized? + * Checks that all time registers are approximately close but not exact, + * given that the RTT may varying for a control packet transaction. + * \return true when all motherboards time registers are in sync + */ + virtual bool get_time_synchronized(void) = 0; + + /*! + * Set the time at which the control commands will take effect. + * + * A timed command will back-pressure all subsequent timed commands, + * assuming that the subsequent commands occur within the time-window. + * If the time spec is late, the command will be activated upon arrival. + * + * \param time_spec the time at which the next command will activate + * \param mboard which motherboard to set the config + */ + virtual void set_command_time(const uhd::time_spec_t &time_spec, size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Clear the command time so future commands are sent ASAP. + * + * \param mboard which motherboard to set the config + */ + virtual void clear_command_time(size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Issue a stream command to the usrp device. + * This tells the usrp to send samples into the host. + * See the documentation for stream_cmd_t for more info. + * + * With multiple devices, the first stream command in a chain of commands + * should have a time spec in the near future and stream_now = false; + * to ensure that the packets can be aligned by their time specs. + * + * \param stream_cmd the stream command to issue + * \param chan the channel index 0 to N-1 + */ + virtual void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t chan = ALL_CHANS) = 0; + + /*! + * Set the clock configuration for the usrp device. + * DEPRECATED in favor of set time and clock source calls. + * This tells the usrp how to get a 10Mhz reference and PPS clock. + * See the documentation for clock_config_t for more info. + * \param clock_config the clock configuration to set + * \param mboard which motherboard to set the config + */ + virtual void set_clock_config(const clock_config_t &clock_config, size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Set the time source for the usrp device. + * This sets the method of time synchronization, + * typically a pulse per second or an encoded time. + * Typical options for source: external, MIMO. + * \param source a string representing the time source + * \param mboard which motherboard to set the config + */ + virtual void set_time_source(const std::string &source, const size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Get the currently set time source. + * \param mboard which motherboard to get the config + * \return the string representing the time source + */ + virtual std::string get_time_source(const size_t mboard) = 0; + + /*! + * Get a list of possible time sources. + * \param mboard which motherboard to get the list + * \return a vector of strings for possible settings + */ + virtual std::vector<std::string> get_time_sources(const size_t mboard) = 0; + + /*! + * Set the clock source for the usrp device. + * This sets the source for a 10 Mhz reference clock. + * Typical options for source: internal, external, MIMO. + * \param source a string representing the clock source + * \param mboard which motherboard to set the config + */ + virtual void set_clock_source(const std::string &source, const size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Get the currently set clock source. + * \param mboard which motherboard to get the config + * \return the string representing the clock source + */ + virtual std::string get_clock_source(const size_t mboard) = 0; + + /*! + * Get a list of possible clock sources. + * \param mboard which motherboard to get the list + * \return a vector of strings for possible settings + */ + virtual std::vector<std::string> get_clock_sources(const size_t mboard) = 0; + + /*! + * Get the number of USRP motherboards in this configuration. + */ + virtual size_t get_num_mboards(void) = 0; + + /*! + * Get a motherboard sensor value. + * \param name the name of the sensor + * \param mboard the motherboard index 0 to M-1 + * \return a sensor value object + */ + virtual sensor_value_t get_mboard_sensor(const std::string &name, size_t mboard = 0) = 0; + + /*! + * Get a list of possible motherboard sensor names. + * \param mboard the motherboard index 0 to M-1 + * \return a vector of sensor names + */ + virtual std::vector<std::string> get_mboard_sensor_names(size_t mboard = 0) = 0; + + /*! + * Perform write on the user configuration register bus. These only exist if + * the user has implemented custom setting registers in the device FPGA. + * \param addr 8-bit register address + * \param data 32-bit register value + * \param mboard which motherboard to set the user register + */ + virtual void set_user_register(const boost::uint8_t addr, const boost::uint32_t data, size_t mboard = ALL_MBOARDS) = 0; + + /******************************************************************* + * RX methods + ******************************************************************/ + /*! + * Set the RX frontend specification: + * The subdev spec maps a physical part of a daughter-board to a channel number. + * Set the subdev spec before calling into any methods with a channel number. + * The subdev spec must be the same size across all motherboards. + * \param spec the new frontend specification + * \param mboard the motherboard index 0 to M-1 + */ + virtual void set_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec, size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Get the RX frontend specification. + * \param mboard the motherboard index 0 to M-1 + * \return the frontend specification in use + */ + virtual uhd::usrp::subdev_spec_t get_rx_subdev_spec(size_t mboard = 0) = 0; + + /*! + * Get the number of RX channels in this configuration. + * This is the number of USRPs times the number of RX channels per board, + * where the number of RX channels per board is homogeneous among all USRPs. + */ + virtual size_t get_rx_num_channels(void) = 0; + + /*! + * Get the name of the RX frontend. + * \param chan the channel index 0 to N-1 + * \return the frontend name + */ + virtual std::string get_rx_subdev_name(size_t chan = 0) = 0; + + /*! + * Set the RX sample rate. + * \param rate the rate in Sps + * \param chan the channel index 0 to N-1 + */ + virtual void set_rx_rate(double rate, size_t chan = ALL_CHANS) = 0; + + /*! + * Gets the RX sample rate. + * \param chan the channel index 0 to N-1 + * \return the rate in Sps + */ + virtual double get_rx_rate(size_t chan = 0) = 0; + + /*! + * Get a range of possible RX rates. + * \param chan the channel index 0 to N-1 + * \return the meta range of rates + */ + virtual meta_range_t get_rx_rates(size_t chan = 0) = 0; + + /*! + * Set the RX center frequency. + * \param tune_request tune request instructions + * \param chan the channel index 0 to N-1 + * \return a tune result object + */ + virtual tune_result_t set_rx_freq( + const tune_request_t &tune_request, size_t chan = 0 + ) = 0; + + /*! + * Get the RX center frequency. + * \param chan the channel index 0 to N-1 + * \return the frequency in Hz + */ + virtual double get_rx_freq(size_t chan = 0) = 0; + + /*! + * Get the RX center frequency range. + * \param chan the channel index 0 to N-1 + * \return a frequency range object + */ + virtual freq_range_t get_rx_freq_range(size_t chan = 0) = 0; + + /*! + * Set the RX gain value for the specified gain element. + * For an empty name, distribute across all gain elements. + * \param gain the gain in dB + * \param name the name of the gain element + * \param chan the channel index 0 to N-1 + */ + virtual void set_rx_gain(double gain, const std::string &name, size_t chan = 0) = 0; + + //! A convenience wrapper for setting overall RX gain + void set_rx_gain(double gain, size_t chan = 0){ + return this->set_rx_gain(gain, ALL_GAINS, chan); + } + + /*! + * Get the RX gain value for the specified gain element. + * For an empty name, sum across all gain elements. + * \param name the name of the gain element + * \param chan the channel index 0 to N-1 + * \return the gain in dB + */ + virtual double get_rx_gain(const std::string &name, size_t chan = 0) = 0; + + //! A convenience wrapper for getting overall RX gain + double get_rx_gain(size_t chan = 0){ + return this->get_rx_gain(ALL_GAINS, chan); + } + + /*! + * Get the RX gain range for the specified gain element. + * For an empty name, calculate the overall gain range. + * \param name the name of the gain element + * \param chan the channel index 0 to N-1 + * \return a gain range object + */ + virtual gain_range_t get_rx_gain_range(const std::string &name, size_t chan = 0) = 0; + + //! A convenience wrapper for getting overall RX gain range + gain_range_t get_rx_gain_range(size_t chan = 0){ + return this->get_rx_gain_range(ALL_GAINS, chan); + } + + /*! + * Get the names of the gain elements in the RX chain. + * Gain elements are ordered from antenna to FPGA. + * \param chan the channel index 0 to N-1 + * \return a vector of gain element names + */ + virtual std::vector<std::string> get_rx_gain_names(size_t chan = 0) = 0; + + /*! + * Select the RX antenna on the frontend. + * \param ant the antenna name + * \param chan the channel index 0 to N-1 + */ + virtual void set_rx_antenna(const std::string &ant, size_t chan = 0) = 0; + + /*! + * Get the selected RX antenna on the frontend. + * \param chan the channel index 0 to N-1 + * \return the antenna name + */ + virtual std::string get_rx_antenna(size_t chan = 0) = 0; + + /*! + * Get a list of possible RX antennas on the frontend. + * \param chan the channel index 0 to N-1 + * \return a vector of antenna names + */ + virtual std::vector<std::string> get_rx_antennas(size_t chan = 0) = 0; + + /*! + * Get the locked status of the LO on the frontend. + * \param chan the channel index 0 to N-1 + * \return true for locked + */ + UHD_DEPRECATED bool get_rx_lo_locked(size_t chan = 0){ + return this->get_rx_sensor("lo_locked", chan).to_bool(); + } + + /*! + * Set the RX bandwidth on the frontend. + * \param bandwidth the bandwidth in Hz + * \param chan the channel index 0 to N-1 + */ + virtual void set_rx_bandwidth(double bandwidth, size_t chan = 0) = 0; + + /*! + * Get the RX bandwidth on the frontend. + * \param chan the channel index 0 to N-1 + * \return the bandwidth in Hz + */ + virtual double get_rx_bandwidth(size_t chan = 0) = 0; + + /*! + * Get the range of the possible RX bandwidth settings. + * \param chan the channel index 0 to N-1 + * \return a range of bandwidths in Hz + */ + virtual meta_range_t get_rx_bandwidth_range(size_t chan = 0) = 0; + + /*! + * Read the RSSI value on the RX frontend. + * \param chan the channel index 0 to N-1 + * \return the rssi in dB + * \throw exception if RSSI readback not supported + */ + UHD_DEPRECATED double read_rssi(size_t chan = 0){ + return this->get_rx_sensor("rssi", chan).to_real(); + } + + /*! + * Get the dboard interface object for the RX frontend. + * The dboard interface gives access to GPIOs, SPI, I2C, low-speed ADC and DAC. + * Use at your own risk! + * \param chan the channel index 0 to N-1 + * \return the dboard interface sptr + */ + virtual dboard_iface::sptr get_rx_dboard_iface(size_t chan = 0) = 0; + + /*! + * Get an RX frontend sensor value. + * \param name the name of the sensor + * \param chan the channel index 0 to N-1 + * \return a sensor value object + */ + virtual sensor_value_t get_rx_sensor(const std::string &name, size_t chan = 0) = 0; + + /*! + * Get a list of possible RX frontend sensor names. + * \param chan the channel index 0 to N-1 + * \return a vector of sensor names + */ + virtual std::vector<std::string> get_rx_sensor_names(size_t chan = 0) = 0; + + /*! + * Enable/disable the automatic RX DC offset correction. + * The automatic correction subtracts out the long-run average. + * + * When disabled, the averaging option operation is halted. + * Once halted, the average value will be held constant + * until the user re-enables the automatic correction + * or overrides the value by manually setting the offset. + * + * \param enb true to enable automatic DC offset correction + * \param chan the channel index 0 to N-1 + */ + virtual void set_rx_dc_offset(const bool enb, size_t chan = ALL_CHANS) = 0; + + /*! + * Set a constant RX DC offset value. + * The value is complex to control both I and Q. + * Only set this when automatic correction is disabled. + * \param offset the dc offset (1.0 is full-scale) + * \param chan the channel index 0 to N-1 + */ + virtual void set_rx_dc_offset(const std::complex<double> &offset, size_t chan = ALL_CHANS) = 0; + + /*! + * Set the RX frontend IQ imbalance correction. + * Use this to adjust the magnitude and phase of I and Q. + * + * \param correction the complex correction (1.0 is full-scale) + * \param chan the channel index 0 to N-1 + */ + virtual void set_rx_iq_balance(const std::complex<double> &correction, size_t chan = ALL_CHANS) = 0; + + /******************************************************************* + * TX methods + ******************************************************************/ + /*! + * Set the TX frontend specification: + * The subdev spec maps a physical part of a daughter-board to a channel number. + * Set the subdev spec before calling into any methods with a channel number. + * The subdev spec must be the same size across all motherboards. + * \param spec the new frontend specification + * \param mboard the motherboard index 0 to M-1 + */ + virtual void set_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec, size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Get the TX frontend specification. + * \param mboard the motherboard index 0 to M-1 + * \return the frontend specification in use + */ + virtual uhd::usrp::subdev_spec_t get_tx_subdev_spec(size_t mboard = 0) = 0; + + /*! + * Get the number of TX channels in this configuration. + * This is the number of USRPs times the number of TX channels per board, + * where the number of TX channels per board is homogeneous among all USRPs. + */ + virtual size_t get_tx_num_channels(void) = 0; + + /*! + * Get the name of the TX frontend. + * \param chan the channel index 0 to N-1 + * \return the frontend name + */ + virtual std::string get_tx_subdev_name(size_t chan = 0) = 0; + + /*! + * Set the TX sample rate. + * \param rate the rate in Sps + * \param chan the channel index 0 to N-1 + */ + virtual void set_tx_rate(double rate, size_t chan = ALL_CHANS) = 0; + + /*! + * Gets the TX sample rate. + * \param chan the channel index 0 to N-1 + * \return the rate in Sps + */ + virtual double get_tx_rate(size_t chan = 0) = 0; + + /*! + * Get a range of possible TX rates. + * \param chan the channel index 0 to N-1 + * \return the meta range of rates + */ + virtual meta_range_t get_tx_rates(size_t chan = 0) = 0; + + /*! + * Set the TX center frequency. + * \param tune_request tune request instructions + * \param chan the channel index 0 to N-1 + * \return a tune result object + */ + virtual tune_result_t set_tx_freq( + const tune_request_t &tune_request, size_t chan = 0 + ) = 0; + + /*! + * Get the TX center frequency. + * \param chan the channel index 0 to N-1 + * \return the frequency in Hz + */ + virtual double get_tx_freq(size_t chan = 0) = 0; + + /*! + * Get the TX center frequency range. + * \param chan the channel index 0 to N-1 + * \return a frequency range object + */ + virtual freq_range_t get_tx_freq_range(size_t chan = 0) = 0; + + /*! + * Set the TX gain value for the specified gain element. + * For an empty name, distribute across all gain elements. + * \param gain the gain in dB + * \param name the name of the gain element + * \param chan the channel index 0 to N-1 + */ + virtual void set_tx_gain(double gain, const std::string &name, size_t chan = 0) = 0; + + //! A convenience wrapper for setting overall TX gain + void set_tx_gain(double gain, size_t chan = 0){ + return this->set_tx_gain(gain, ALL_GAINS, chan); + } + + /*! + * Get the TX gain value for the specified gain element. + * For an empty name, sum across all gain elements. + * \param name the name of the gain element + * \param chan the channel index 0 to N-1 + * \return the gain in dB + */ + virtual double get_tx_gain(const std::string &name, size_t chan = 0) = 0; + + //! A convenience wrapper for getting overall TX gain + double get_tx_gain(size_t chan = 0){ + return this->get_tx_gain(ALL_GAINS, chan); + } + + /*! + * Get the TX gain range for the specified gain element. + * For an empty name, calculate the overall gain range. + * \param name the name of the gain element + * \param chan the channel index 0 to N-1 + * \return a gain range object + */ + virtual gain_range_t get_tx_gain_range(const std::string &name, size_t chan = 0) = 0; + + //! A convenience wrapper for getting overall TX gain range + gain_range_t get_tx_gain_range(size_t chan = 0){ + return this->get_tx_gain_range(ALL_GAINS, chan); + } + + /*! + * Get the names of the gain elements in the TX chain. + * Gain elements are ordered from antenna to FPGA. + * \param chan the channel index 0 to N-1 + * \return a vector of gain element names + */ + virtual std::vector<std::string> get_tx_gain_names(size_t chan = 0) = 0; + + /*! + * Select the TX antenna on the frontend. + * \param ant the antenna name + * \param chan the channel index 0 to N-1 + */ + virtual void set_tx_antenna(const std::string &ant, size_t chan = 0) = 0; + + /*! + * Get the selected TX antenna on the frontend. + * \param chan the channel index 0 to N-1 + * \return the antenna name + */ + virtual std::string get_tx_antenna(size_t chan = 0) = 0; + + /*! + * Get a list of possible TX antennas on the frontend. + * \param chan the channel index 0 to N-1 + * \return a vector of antenna names + */ + virtual std::vector<std::string> get_tx_antennas(size_t chan = 0) = 0; + + /*! + * Get the locked status of the LO on the frontend. + * \param chan the channel index 0 to N-1 + * \return true for locked + */ + UHD_DEPRECATED bool get_tx_lo_locked(size_t chan = 0){ + return this->get_tx_sensor("lo_locked", chan).to_bool(); + } + + /*! + * Set the TX bandwidth on the frontend. + * \param bandwidth the bandwidth in Hz + * \param chan the channel index 0 to N-1 + */ + virtual void set_tx_bandwidth(double bandwidth, size_t chan = 0) = 0; + + /*! + * Get the TX bandwidth on the frontend. + * \param chan the channel index 0 to N-1 + * \return the bandwidth in Hz + */ + virtual double get_tx_bandwidth(size_t chan = 0) = 0; + + /*! + * Get the range of the possible TX bandwidth settings. + * \param chan the channel index 0 to N-1 + * \return a range of bandwidths in Hz + */ + virtual meta_range_t get_tx_bandwidth_range(size_t chan = 0) = 0; + + /*! + * Get the dboard interface object for the TX frontend. + * The dboard interface gives access to GPIOs, SPI, I2C, low-speed ADC and DAC. + * Use at your own risk! + * \param chan the channel index 0 to N-1 + * \return the dboard interface sptr + */ + virtual dboard_iface::sptr get_tx_dboard_iface(size_t chan = 0) = 0; + + /*! + * Get an TX frontend sensor value. + * \param name the name of the sensor + * \param chan the channel index 0 to N-1 + * \return a sensor value object + */ + virtual sensor_value_t get_tx_sensor(const std::string &name, size_t chan = 0) = 0; + + /*! + * Get a list of possible TX frontend sensor names. + * \param chan the channel index 0 to N-1 + * \return a vector of sensor names + */ + virtual std::vector<std::string> get_tx_sensor_names(size_t chan = 0) = 0; + + /*! + * Set a constant TX DC offset value. + * The value is complex to control both I and Q. + * \param offset the dc offset (1.0 is full-scale) + * \param chan the channel index 0 to N-1 + */ + virtual void set_tx_dc_offset(const std::complex<double> &offset, size_t chan = ALL_CHANS) = 0; + + /*! + * Set the TX frontend IQ imbalance correction. + * Use this to adjust the magnitude and phase of I and Q. + * + * \param correction the complex correction (1.0 is full-scale) + * \param chan the channel index 0 to N-1 + */ + virtual void set_tx_iq_balance(const std::complex<double> &correction, size_t chan = ALL_CHANS) = 0; + +}; + +}} + +#endif /* INCLUDED_UHD_USRP_MULTI_USRP_HPP */ diff --git a/host/include/uhd/usrp/subdev_spec.hpp b/host/include/uhd/usrp/subdev_spec.hpp new file mode 100644 index 000000000..62c1fc177 --- /dev/null +++ b/host/include/uhd/usrp/subdev_spec.hpp @@ -0,0 +1,91 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_USRP_SUBDEV_SPEC_HPP +#define INCLUDED_UHD_USRP_SUBDEV_SPEC_HPP + +#include <uhd/config.hpp> +#include <boost/operators.hpp> +#include <vector> +#include <string> + +namespace uhd{ namespace usrp{ + + /*! + * A subdevice specification (daughterboard slot, subdevice) name pairing. + */ + struct UHD_API subdev_spec_pair_t : boost::equality_comparable<subdev_spec_pair_t>{ + //! The daughterboard slot name + std::string db_name; + + //! The subdevice name + std::string sd_name; + + /*! + * Create a new subdevice specification pair from dboard and subdev names. + * \param db_name the name of a daughterboard slot + * \param sd_name the name of a subdevice on that daughterboard + */ + subdev_spec_pair_t( + const std::string &db_name = "", + const std::string &sd_name = "" + ); + }; + + //! overloaded comparison operator for subdev_spec_pair_t + UHD_API bool operator==(const subdev_spec_pair_t &, const subdev_spec_pair_t &); + + /*! + * A list of (daughterboard slot name, subdevice name) pairs: + * + * A subdevice specification represents a list of subdevices on a motherboard. + * The subdevices specified may span across multiple daughterboards; + * Hence the need for a subdevice specification over a simple list of strings. + * Typically, the user will pass a RX or TX subdevice specification into the API, + * and the implementation will infer the channel configuration from the specification. + * + * The subdevice specification can be represented as a markup-string. + * The markup-string is a whitespace separated list of dboard:subdev pairs. + * The first pair represents the subdevice for channel zero, + * the second pair represents the subdevice for channel one, and so on. + */ + class UHD_API subdev_spec_t : public std::vector<subdev_spec_pair_t>{ + public: + + /*! + * Create a subdev specification from a markup string. + * \param markup the markup string + */ + subdev_spec_t(const std::string &markup = ""); + + /*! + * Convert a subdev specification into a pretty print string. + * \return a printable string representing the subdev specification + */ + std::string to_pp_string(void) const; + + /*! + * Convert the subdevice specification into a markup string. + * The markup string contains the delimiter symbols. + * \return a string with delimiter markup + */ + std::string to_string(void) const; + }; + +}} + +#endif /* INCLUDED_UHD_USRP_SUBDEV_SPEC_HPP */ diff --git a/host/include/uhd/utils/CMakeLists.txt b/host/include/uhd/utils/CMakeLists.txt new file mode 100644 index 000000000..5a434fd9a --- /dev/null +++ b/host/include/uhd/utils/CMakeLists.txt @@ -0,0 +1,38 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +INSTALL(FILES + algorithm.hpp + assert_has.hpp + assert_has.ipp + byteswap.hpp + byteswap.ipp + csv.hpp + gain_group.hpp + images.hpp + log.hpp + msg.hpp + paths.hpp + pimpl.hpp + safe_call.hpp + safe_main.hpp + static.hpp + tasks.hpp + thread_priority.hpp + DESTINATION ${INCLUDE_DIR}/uhd/utils + COMPONENT headers +) diff --git a/host/include/uhd/utils/algorithm.hpp b/host/include/uhd/utils/algorithm.hpp new file mode 100644 index 000000000..5598d862b --- /dev/null +++ b/host/include/uhd/utils/algorithm.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_ALGORITHM_HPP +#define INCLUDED_UHD_UTILS_ALGORITHM_HPP + +#include <algorithm> +#include <boost/range/begin.hpp> +#include <boost/range/end.hpp> + +/*! + * Useful templated functions and classes that I like to pretend are part of stl. + * Many of the range wrapper functions come with recent versions of boost (1.43). + */ +namespace uhd{ + + /*! + * A wrapper around std::sort that takes a range instead of an iterator. + * + * The elements are sorted into ascending order using the less-than operator. + * This wrapper sorts the elements non-destructively into a new range. + * Based on the builtin python function sorted(...) + * + * \param range the range of elements to be sorted + * \return a new range with the elements sorted + */ + template<typename Range> inline Range sorted(const Range &range){ + Range r(range); std::sort(boost::begin(r), boost::end(r)); return r; + } + + /*! + * A wrapper around std::reverse that takes a range instead of an iterator. + * + * The elements are reversed into descending order using the less-than operator. + * This wrapper reverses the elements non-destructively into a new range. + * Based on the builtin python function reversed(...) + * + * \param range the range of elements to be reversed + * \return a new range with the elements reversed + */ + template<typename Range> inline Range reversed(const Range &range){ + Range r(range); std::reverse(boost::begin(r), boost::end(r)); return r; + } + + /*! + * Is the value found within the elements in this range? + * + * Uses std::find to search the iterable for an element. + * + * \param range the elements to search through + * \param value the match to look for in the range + * \return true when the value is found in the range + */ + template<typename Range, typename T> inline + bool has(const Range &range, const T &value){ + return boost::end(range) != std::find(boost::begin(range), boost::end(range), value); + } + + /*! + * A templated clip implementation. + * \param val the value to clip between an upper and lower limit + * \param bound1 the upper or lower bound + * \param bound2 the upper or lower bound + * \return the value clipped at the bounds + */ + template<typename T> inline T clip(const T &val, const T &bound1, const T &bound2){ + const T minimum = std::min(bound1, bound2); + if (val < minimum) return minimum; + const T maximum = std::max(bound1, bound2); + if (val > maximum) return maximum; + return val; + } + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_ALGORITHM_HPP */ diff --git a/host/include/uhd/utils/assert_has.hpp b/host/include/uhd/utils/assert_has.hpp new file mode 100644 index 000000000..eae7652ad --- /dev/null +++ b/host/include/uhd/utils/assert_has.hpp @@ -0,0 +1,47 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_ASSERT_HAS_HPP +#define INCLUDED_UHD_UTILS_ASSERT_HAS_HPP + +#include <uhd/config.hpp> +#include <string> + +namespace uhd{ + + /*! + * Check that an element is found in a container. + * If not, throw a meaningful assertion error. + * The "what" in the error will show what is + * being set and a list of known good values. + * + * \param range a list of possible settings + * \param value an element that may be in the list + * \param what a description of what the value is + * \throw assertion_error when elem not in list + */ + template<typename T, typename Range> void assert_has( + const Range &range, + const T &value, + const std::string &what = "unknown" + ); + +}//namespace uhd + +#include <uhd/utils/assert_has.ipp> + +#endif /* INCLUDED_UHD_UTILS_ASSERT_HAS_HPP */ diff --git a/host/include/uhd/utils/assert_has.ipp b/host/include/uhd/utils/assert_has.ipp new file mode 100644 index 000000000..7b3c88cb7 --- /dev/null +++ b/host/include/uhd/utils/assert_has.ipp @@ -0,0 +1,53 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_ASSERT_HAS_IPP +#define INCLUDED_UHD_UTILS_ASSERT_HAS_IPP + +#include <uhd/utils/algorithm.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> + +namespace uhd{ + + template<typename T, typename Range> UHD_INLINE void assert_has( + const Range &range, + const T &value, + const std::string &what + ){ + if (uhd::has(range, value)) return; + std::string possible_values = ""; + size_t i = 0; + BOOST_FOREACH(const T &v, range){ + if (i++ > 0) possible_values += ", "; + possible_values += boost::lexical_cast<std::string>(v); + } + throw uhd::assertion_error(str(boost::format( + "assertion failed:\n" + " %s is not a valid %s.\n" + " possible values are: [%s].\n" + ) + % boost::lexical_cast<std::string>(value) + % what % possible_values + )); + } + +}//namespace uhd + +#endif /* INCLUDED_UHD_UTILS_ASSERT_HAS_IPP */ diff --git a/host/include/uhd/utils/byteswap.hpp b/host/include/uhd/utils/byteswap.hpp new file mode 100644 index 000000000..2b5a46c66 --- /dev/null +++ b/host/include/uhd/utils/byteswap.hpp @@ -0,0 +1,56 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_BYTESWAP_HPP +#define INCLUDED_UHD_UTILS_BYTESWAP_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> + +/*! \file byteswap.hpp + * Provide fast byteswaping routines for 16, 32, and 64 bit integers, + * by using the system's native routines/intrinsics when available. + */ + +namespace uhd{ + + //! perform a byteswap on a 16 bit integer + boost::uint16_t byteswap(boost::uint16_t); + + //! perform a byteswap on a 32 bit integer + boost::uint32_t byteswap(boost::uint32_t); + + //! perform a byteswap on a 64 bit integer + boost::uint64_t byteswap(boost::uint64_t); + + //! network to host: short, long, or long-long + template<typename T> T ntohx(T); + + //! host to network: short, long, or long-long + template<typename T> T htonx(T); + + //! worknet to host: short, long, or long-long + template<typename T> T wtohx(T); + + //! host to worknet: short, long, or long-long + template<typename T> T htowx(T); + +} //namespace uhd + +#include <uhd/utils/byteswap.ipp> + +#endif /* INCLUDED_UHD_UTILS_BYTESWAP_HPP */ diff --git a/host/include/uhd/utils/byteswap.ipp b/host/include/uhd/utils/byteswap.ipp new file mode 100644 index 000000000..5b2a7252f --- /dev/null +++ b/host/include/uhd/utils/byteswap.ipp @@ -0,0 +1,136 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_BYTESWAP_IPP +#define INCLUDED_UHD_UTILS_BYTESWAP_IPP + +/*********************************************************************** + * Platform-specific implementation details for byteswap below: + **********************************************************************/ +#if defined(BOOST_MSVC) //http://msdn.microsoft.com/en-us/library/a3140177%28VS.80%29.aspx + #include <cstdlib> + + UHD_INLINE boost::uint16_t uhd::byteswap(boost::uint16_t x){ + return _byteswap_ushort(x); + } + + UHD_INLINE boost::uint32_t uhd::byteswap(boost::uint32_t x){ + return _byteswap_ulong(x); + } + + UHD_INLINE boost::uint64_t uhd::byteswap(boost::uint64_t x){ + return _byteswap_uint64(x); + } + +#elif defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 + + UHD_INLINE boost::uint16_t uhd::byteswap(boost::uint16_t x){ + return (x>>8) | (x<<8); //DNE return __builtin_bswap16(x); + } + + UHD_INLINE boost::uint32_t uhd::byteswap(boost::uint32_t x){ + return __builtin_bswap32(x); + } + + UHD_INLINE boost::uint64_t uhd::byteswap(boost::uint64_t x){ + return __builtin_bswap64(x); + } + +#elif defined(UHD_PLATFORM_MACOS) + #include <libkern/OSByteOrder.h> + + UHD_INLINE boost::uint16_t uhd::byteswap(boost::uint16_t x){ + return OSSwapInt16(x); + } + + UHD_INLINE boost::uint32_t uhd::byteswap(boost::uint32_t x){ + return OSSwapInt32(x); + } + + UHD_INLINE boost::uint64_t uhd::byteswap(boost::uint64_t x){ + return OSSwapInt64(x); + } + +#elif defined(UHD_PLATFORM_LINUX) + #include <byteswap.h> + + UHD_INLINE boost::uint16_t uhd::byteswap(boost::uint16_t x){ + return bswap_16(x); + } + + UHD_INLINE boost::uint32_t uhd::byteswap(boost::uint32_t x){ + return bswap_32(x); + } + + UHD_INLINE boost::uint64_t uhd::byteswap(boost::uint64_t x){ + return bswap_64(x); + } + +#else //http://www.koders.com/c/fidB93B34CD44F0ECF724F1A4EAE3854BA2FE692F59.aspx + + UHD_INLINE boost::uint16_t uhd::byteswap(boost::uint16_t x){ + return (x>>8) | (x<<8); + } + + UHD_INLINE boost::uint32_t uhd::byteswap(boost::uint32_t x){ + return (boost::uint32_t(uhd::byteswap(boost::uint16_t(x&0xfffful)))<<16) | (uhd::byteswap(boost::uint16_t(x>>16))); + } + + UHD_INLINE boost::uint64_t uhd::byteswap(boost::uint64_t x){ + return (boost::uint64_t(uhd::byteswap(boost::uint32_t(x&0xffffffffull)))<<32) | (uhd::byteswap(boost::uint32_t(x>>32))); + } + +#endif + +/*********************************************************************** + * Define the templated network to/from host conversions + **********************************************************************/ +#include <boost/detail/endian.hpp> + +template<typename T> UHD_INLINE T uhd::ntohx(T num){ + #ifdef BOOST_BIG_ENDIAN + return num; + #else + return uhd::byteswap(num); + #endif +} + +template<typename T> UHD_INLINE T uhd::htonx(T num){ + #ifdef BOOST_BIG_ENDIAN + return num; + #else + return uhd::byteswap(num); + #endif +} + +template<typename T> UHD_INLINE T uhd::wtohx(T num){ + #ifdef BOOST_BIG_ENDIAN + return uhd::byteswap(num); + #else + return num; + #endif +} + +template<typename T> UHD_INLINE T uhd::htowx(T num){ + #ifdef BOOST_BIG_ENDIAN + return uhd::byteswap(num); + #else + return num; + #endif +} + +#endif /* INCLUDED_UHD_UTILS_BYTESWAP_IPP */ diff --git a/host/include/uhd/utils/csv.hpp b/host/include/uhd/utils/csv.hpp new file mode 100644 index 000000000..8dbb28377 --- /dev/null +++ b/host/include/uhd/utils/csv.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_CSV_HPP +#define INCLUDED_UHD_UTILS_CSV_HPP + +#include <uhd/config.hpp> +#include <vector> +#include <string> +#include <istream> + +namespace uhd{ namespace csv{ + typedef std::vector<std::string> row_type; + typedef std::vector<row_type> rows_type; + + //! Convert an input stream to csv rows. + UHD_API rows_type to_rows(std::istream &input); + +}} //namespace uhd::csv + +#endif /* INCLUDED_UHD_UTILS_CSV_HPP */ diff --git a/host/include/uhd/utils/gain_group.hpp b/host/include/uhd/utils/gain_group.hpp new file mode 100644 index 000000000..7ef7bdcf5 --- /dev/null +++ b/host/include/uhd/utils/gain_group.hpp @@ -0,0 +1,108 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_GAIN_GROUP_HPP +#define INCLUDED_UHD_UTILS_GAIN_GROUP_HPP + +#include <uhd/config.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <boost/utility.hpp> +#include <vector> +#include <string> + +namespace uhd{ + +/*! + * A set of function to control a gain element. + */ +struct UHD_API gain_fcns_t{ + boost::function<gain_range_t(void)> get_range; + boost::function<double(void)> get_value; + boost::function<void(double)> set_value; +}; + +class UHD_API gain_group : boost::noncopyable{ +public: + typedef boost::shared_ptr<gain_group> sptr; + + /*! + * Get the gain range for the gain element specified by name. + * For an empty name, get the overall gain range for this group. + * Overall step is defined as the minimum step size. + * \param name name of the gain element (optional) + * \return a gain range with overall min, max, step + */ + virtual gain_range_t get_range(const std::string &name = "") = 0; + + /*! + * Get the gain value for the gain element specified by name. + * For an empty name, get the overall gain value for this group. + * \param name name of the gain element (optional) + * \return a gain value of the element or all elements + */ + virtual double get_value(const std::string &name = "") = 0; + + /*! + * Set the gain value for the gain element specified by name. + * For an empty name, set the overall gain value for this group. + * The power will be distributed across individual gain elements. + * The semantics of how to do this are determined by the priority. + * \param gain the gain to set for the lement or across the group + * \param name name of the gain element (optional) + */ + virtual void set_value(double gain, const std::string &name = "") = 0; + + /*! + * Get a list of names of registered gain elements. + * The names are in the order that they were registered. + * \return a vector of gain name strings + */ + virtual const std::vector<std::string> get_names(void) = 0; + + /*! + * Register a set of gain functions into this group: + * + * The name should be a unique and non-empty name. + * Othwerwise, the implementation will rename it. + * + * Priority determines how power will be distributed + * with higher priorities getting the power first, + * and lower priorities getting the remainder power. + * + * \param name the name of the gain element + * \param gain_fcns the set of gain functions + * \param priority the priority of the gain element + */ + virtual void register_fcns( + const std::string &name, + const gain_fcns_t &gain_fcns, + size_t priority = 0 + ) = 0; + + /*! + * Make a new empty gain group. + * \return a gain group object. + */ + static sptr make(void); +}; + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_GAIN_GROUP_HPP */ + diff --git a/host/include/uhd/utils/images.hpp b/host/include/uhd/utils/images.hpp new file mode 100644 index 000000000..8b5a1eedd --- /dev/null +++ b/host/include/uhd/utils/images.hpp @@ -0,0 +1,38 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_IMAGES_HPP +#define INCLUDED_UHD_UTILS_IMAGES_HPP + +#include <uhd/config.hpp> +#include <string> + +namespace uhd{ + + /*! + * Search for an image in the system image paths: + * Search compiled-in paths and environment variable paths + * for a specific image file with the provided file name. + * \param image_name the name of the file + * \return the full system path to the file + * \throw exception if the image was not found + */ + UHD_API std::string find_image_path(const std::string &image_name); + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_IMAGES_HPP */ diff --git a/host/include/uhd/utils/log.hpp b/host/include/uhd/utils/log.hpp new file mode 100644 index 000000000..1f515c15e --- /dev/null +++ b/host/include/uhd/utils/log.hpp @@ -0,0 +1,97 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_LOG_HPP +#define INCLUDED_UHD_UTILS_LOG_HPP + +#include <uhd/config.hpp> +#include <uhd/utils/pimpl.hpp> +#include <boost/current_function.hpp> +#include <ostream> +#include <string> + +/*! \file log.hpp + * The UHD logging facility. + * + * The logger enables UHD library code to easily log events into a file. + * Log entries are time-stamped and stored with file, line, and function. + * Each call to the UHD_LOG macros is synchronous and thread-safe. + * + * The log file can be found in the path <temp-directory>/uhd.log, + * where <temp-directory> is the user or system's temporary directory. + * To override <temp-directory>, set the UHD_TEMP_PATH environment variable. + * + * All log messages with verbosity greater than or equal to the log level + * (in other words, as often or less often than the current log level) + * are recorded into the log file. All other messages are sent to null. + * + * The default log level is "never", but can be overridden: + * - at compile time by setting the pre-processor define UHD_LOG_LEVEL. + * - at runtime by setting the environment variable UHD_LOG_LEVEL. + * + * UHD_LOG_LEVEL can be the name of a verbosity enum or integer value: + * - Example pre-processor define: -DUHD_LOG_LEVEL=3 + * - Example pre-processor define: -DUHD_LOG_LEVEL=regularly + * - Example environment variable: export UHD_LOG_LEVEL=3 + * - Example environment variable: export UHD_LOG_LEVEL=regularly + */ + +/*! + * A UHD logger macro with configurable verbosity. + * Usage: UHD_LOGV(very_rarely) << "the log message" << std::endl; + */ +#define UHD_LOGV(verbosity) \ + uhd::_log::log(uhd::_log::verbosity, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION)() + +/*! + * A UHD logger macro with default verbosity. + * Usage: UHD_LOG << "the log message" << std::endl; + */ +#define UHD_LOG \ + UHD_LOGV(regularly) + + +namespace uhd{ namespace _log{ + + //! Verbosity levels for the logger + enum verbosity_t{ + always = 1, + often = 2, + regularly = 3, + rarely = 4, + very_rarely = 5, + never = 6, + }; + + //! Internal logging object (called by UHD_LOG macros) + class UHD_API log{ + public: + log( + const verbosity_t verbosity, + const std::string &file, + const unsigned int line, + const std::string &function + ); + ~log(void); + std::ostream &operator()(void); + private: + UHD_PIMPL_DECL(impl) _impl; + }; + +}} //namespace uhd::_log + +#endif /* INCLUDED_UHD_UTILS_LOG_HPP */ diff --git a/host/include/uhd/utils/msg.hpp b/host/include/uhd/utils/msg.hpp new file mode 100644 index 000000000..3aac9577a --- /dev/null +++ b/host/include/uhd/utils/msg.hpp @@ -0,0 +1,74 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_MSG_HPP +#define INCLUDED_UHD_UTILS_MSG_HPP + +#include <uhd/config.hpp> +#include <uhd/utils/pimpl.hpp> +#include <ostream> +#include <string> + +/*! + * A UHD message macro with configurable type. + * Usage: UHD_MSG(warning) << "some warning message" << std::endl; + */ +#define UHD_MSG(type) \ + uhd::msg::_msg(uhd::msg::type)() + +//! Helpful debug tool to print site info +#define UHD_HERE() \ + UHD_MSG(status) << __FILE__ << ":" << __LINE__ << std::endl + +//! Helpful debug tool to print a variable +#define UHD_VAR(var) \ + UHD_MSG(status) << #var << " = " << var << std::endl; + +namespace uhd{ namespace msg{ + + //! Possible message types + enum type_t{ + status = 's', + warning = 'w', + error = 'e', + fastpath= 'f' + }; + + //! Typedef for a user-registered message handler + typedef void (*handler_t)(type_t, const std::string &); + + /*! + * Register the handler for uhd system messages. + * Only one handler can be registered at once. + * This replaces the default std::cout/cerr handler. + * \param handler a new handler callback function + */ + UHD_API void register_handler(const handler_t &handler); + + //! Internal message object (called by UHD_MSG macro) + class UHD_API _msg{ + public: + _msg(const type_t type); + ~_msg(void); + std::ostream &operator()(void); + private: + UHD_PIMPL_DECL(impl) _impl; + }; + +}} //namespace uhd::msg + +#endif /* INCLUDED_UHD_UTILS_MSG_HPP */ diff --git a/host/include/uhd/utils/paths.hpp b/host/include/uhd/utils/paths.hpp new file mode 100644 index 000000000..2261f1b77 --- /dev/null +++ b/host/include/uhd/utils/paths.hpp @@ -0,0 +1,34 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_PATHS_HPP +#define INCLUDED_UHD_UTILS_PATHS_HPP + +#include <uhd/config.hpp> +#include <string> + +namespace uhd{ + + //! Get a string representing the system's temporary directory + UHD_API std::string get_tmp_path(void); + + //! Get a string representing the system's appdata directory + UHD_API std::string get_app_path(void); + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_PATHS_HPP */ diff --git a/host/include/uhd/utils/pimpl.hpp b/host/include/uhd/utils/pimpl.hpp new file mode 100644 index 000000000..09bf0c0a2 --- /dev/null +++ b/host/include/uhd/utils/pimpl.hpp @@ -0,0 +1,55 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_PIMPL_HPP +#define INCLUDED_UHD_UTILS_PIMPL_HPP + +#include <uhd/config.hpp> +#include <boost/shared_ptr.hpp> + +/*! \file pimpl.hpp + * "Pimpl idiom" (pointer to implementation idiom). + * The UHD_PIMPL_* macros simplify code overhead for declaring and making pimpls. + * + * Each pimpl is implemented as a shared pointer to the implementation: + * - The container class will not have to deallocate the pimpl. + * - The container class will use the pimpl as a regular pointer. + * - Usage: _impl->method(arg0, arg1) + * - Usage: _impl->member = value; + * + * \see http://en.wikipedia.org/wiki/Opaque_pointer + */ + +/*! + * Make a declaration for a pimpl in a header file. + * - Usage: UHD_PIMPL_DECL(impl) _impl; + * \param _name the name of the pimpl class + */ +#define UHD_PIMPL_DECL(_name) \ + struct _name; boost::shared_ptr<_name> + +/*! + * Make an instance of a pimpl in a source file. + * - Usage: _impl = UHD_PIMPL_MAKE(impl, ()); + * - Usage: _impl = UHD_PIMPL_MAKE(impl, (a0, a1)); + * \param _name the name of the pimpl class + * \param _args the constructor args for the pimpl + */ +#define UHD_PIMPL_MAKE(_name, _args) \ + boost::shared_ptr<_name>(new _name _args) + +#endif /* INCLUDED_UHD_UTILS_PIMPL_HPP */ diff --git a/host/include/uhd/utils/safe_call.hpp b/host/include/uhd/utils/safe_call.hpp new file mode 100644 index 000000000..ab287cc66 --- /dev/null +++ b/host/include/uhd/utils/safe_call.hpp @@ -0,0 +1,45 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_SAFE_CALL_HPP +#define INCLUDED_UHD_UTILS_SAFE_CALL_HPP + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> + +//! helper macro for safe call to produce warnings +#define _UHD_SAFE_CALL_WARNING(code, what) UHD_LOGV(rarely) << \ + UHD_THROW_SITE_INFO("Exception caught in safe-call.") + #code + " -> " + what \ +; + +/*! + * A safe-call catches all exceptions thrown by code, + * and creates a verbose warning about the exception. + * Usage: UHD_SAFE_CALL(some_code_to_call();) + * \param code the block of code to call safely + */ +#define UHD_SAFE_CALL(code) \ + try{code} \ + catch(const std::exception &e){ \ + _UHD_SAFE_CALL_WARNING(code, e.what()); \ + } \ + catch(...){ \ + _UHD_SAFE_CALL_WARNING(code, "unknown exception"); \ + } + +#endif /* INCLUDED_UHD_UTILS_SAFE_CALL_HPP */ diff --git a/host/include/uhd/utils/safe_main.hpp b/host/include/uhd/utils/safe_main.hpp new file mode 100644 index 000000000..b682aa540 --- /dev/null +++ b/host/include/uhd/utils/safe_main.hpp @@ -0,0 +1,44 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_SAFE_MAIN_HPP +#define INCLUDED_UHD_UTILS_SAFE_MAIN_HPP + +#include <uhd/config.hpp> +#include <iostream> +#include <stdexcept> + +/*! + * Defines a safe wrapper that places a catch-all around main. + * If an exception is thrown, it prints to stderr and returns. + * Usage: int UHD_SAFE_MAIN(int argc, char *argv[]){ main code here } + * \param _argc the declaration for argc + * \param _argv the declaration for argv + */ +#define UHD_SAFE_MAIN(_argc, _argv) _main(int, char*[]); \ +int main(int argc, char *argv[]){ \ + try { \ + return _main(argc, argv); \ + } catch(const std::exception &e) { \ + std::cerr << "Error: " << e.what() << std::endl; \ + } catch(...) { \ + std::cerr << "Error: unknown exception" << std::endl; \ + } \ + return ~0; \ +} int _main(_argc, _argv) + +#endif /* INCLUDED_UHD_UTILS_SAFE_MAIN_HPP */ diff --git a/host/include/uhd/utils/static.hpp b/host/include/uhd/utils/static.hpp new file mode 100644 index 000000000..82cdf36d9 --- /dev/null +++ b/host/include/uhd/utils/static.hpp @@ -0,0 +1,46 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_STATIC_HPP +#define INCLUDED_UHD_UTILS_STATIC_HPP + +#include <uhd/config.hpp> + +/*! + * Defines a function that implements the "construct on first use" idiom + * \param _t the type definition for the instance + * \param _x the name of the defined function + * \return a reference to the lazy instance + */ +#define UHD_SINGLETON_FCN(_t, _x) static _t &_x(){static _t _x; return _x;} + +/*! + * Defines a static code block that will be called before main() + * The static block will catch and print exceptions to std error. + * \param _x the unique name of the fixture (unique per source) + */ +#define UHD_STATIC_BLOCK(_x) \ + void _x(void); \ + static _uhd_static_fixture _x##_fixture(&_x, #_x); \ + void _x(void) + +//! Helper for static block, constructor calls function +struct UHD_API _uhd_static_fixture{ + _uhd_static_fixture(void (*)(void), const char *); +}; + +#endif /* INCLUDED_UHD_UTILS_STATIC_HPP */ diff --git a/host/include/uhd/utils/tasks.hpp b/host/include/uhd/utils/tasks.hpp new file mode 100644 index 000000000..38b2bddf0 --- /dev/null +++ b/host/include/uhd/utils/tasks.hpp @@ -0,0 +1,53 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_TASKS_HPP +#define INCLUDED_UHD_UTILS_TASKS_HPP + +#include <uhd/config.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <boost/utility.hpp> + +namespace uhd{ + + class task : boost::noncopyable{ + public: + typedef boost::shared_ptr<task> sptr; + typedef boost::function<void(void)> task_fcn_type; + + /*! + * Create a new task object with function callback. + * The task function callback will be run in a loop. + * until the thread is interrupted by the deconstructor. + * + * A task should return in a reasonable amount of time + * or may block forever under the following conditions: + * - The blocking call is interruptible. + * - The task polls the interrupt condition. + * + * \param task_fcn the task callback function + * \return a new task object + */ + static sptr make(const task_fcn_type &task_fcn); + + }; + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_TASKS_HPP */ + diff --git a/host/include/uhd/utils/thread_priority.hpp b/host/include/uhd/utils/thread_priority.hpp new file mode 100644 index 000000000..988fc3012 --- /dev/null +++ b/host/include/uhd/utils/thread_priority.hpp @@ -0,0 +1,58 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_THREAD_PRIORITY_HPP +#define INCLUDED_UHD_UTILS_THREAD_PRIORITY_HPP + +#include <uhd/config.hpp> + +namespace uhd{ + + static const float default_thread_priority = float(0.5); + + /*! + * Set the scheduling priority on the current thread. + * + * A new thread or calling process should make this call + * with the defailts this to enable realtime scheduling. + * + * A priority of zero corresponds to normal priority. + * Positive priority values are higher than normal. + * Negative priority values are lower than normal. + * + * \param priority a value between -1 and 1 + * \param realtime true to use realtime mode + * \throw exception on set priority failure + */ + UHD_API void set_thread_priority( + float priority = default_thread_priority, + bool realtime = true + ); + + /*! + * Set the scheduling priority on the current thread. + * Same as set_thread_priority but does not throw on failure. + * \return true on success, false on failure + */ + UHD_API bool set_thread_priority_safe( + float priority = default_thread_priority, + bool realtime = true + ); + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_THREAD_PRIORITY_HPP */ diff --git a/host/include/uhd/version.hpp b/host/include/uhd/version.hpp new file mode 100644 index 000000000..19d672e65 --- /dev/null +++ b/host/include/uhd/version.hpp @@ -0,0 +1,28 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_VERSION_HPP +#define INCLUDED_UHD_VERSION_HPP + +#include <uhd/config.hpp> +#include <string> + +namespace uhd{ + UHD_API std::string get_version_string(void); +} //namespace uhd + +#endif /* INCLUDED_UHD_VERSION_HPP */ diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt new file mode 100644 index 000000000..7a76ebd53 --- /dev/null +++ b/host/lib/CMakeLists.txt @@ -0,0 +1,112 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Helpful Macros +######################################################################## +MACRO(LIBUHD_APPEND_SOURCES) + LIST(APPEND libuhd_sources ${ARGV}) +ENDMACRO(LIBUHD_APPEND_SOURCES) + +MACRO(LIBUHD_APPEND_LIBS) + LIST(APPEND libuhd_libs ${ARGV}) +ENDMACRO(LIBUHD_APPEND_LIBS) + +MACRO(LIBUHD_PYTHON_GEN_SOURCE pyfile outfile) + #ensure that the directory exists for outfile + GET_FILENAME_COMPONENT(outfile_dir ${outfile} PATH) + FILE(MAKE_DIRECTORY ${outfile_dir}) + + #make the outfile depend on the python script + ADD_CUSTOM_COMMAND( + OUTPUT ${outfile} DEPENDS ${pyfile} ${LIBUHD_PYTHON_GEN_SOURCE_DEPS} + COMMAND ${PYTHON_EXECUTABLE} -B ${pyfile} ${outfile} + COMMENT "Generating ${outfile}" + ) + + #make libuhd depend on the outfile + LIBUHD_APPEND_SOURCES(${outfile}) +ENDMACRO(LIBUHD_PYTHON_GEN_SOURCE) + +MACRO(INCLUDE_SUBDIRECTORY subdir) + #insert the current directories on the front of the list + LIST(INSERT _cmake_source_dirs 0 ${CMAKE_CURRENT_SOURCE_DIR}) + LIST(INSERT _cmake_binary_dirs 0 ${CMAKE_CURRENT_BINARY_DIR}) + + #set the current directories to the names of the subdirs + SET(CMAKE_CURRENT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${subdir}) + SET(CMAKE_CURRENT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${subdir}) + + #include the subdirectory CMakeLists to run it + FILE(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt) + + #reset the value of the current directories + LIST(GET _cmake_source_dirs 0 CMAKE_CURRENT_SOURCE_DIR) + LIST(GET _cmake_binary_dirs 0 CMAKE_CURRENT_BINARY_DIR) + + #pop the subdir names of the front of the list + LIST(REMOVE_AT _cmake_source_dirs 0) + LIST(REMOVE_AT _cmake_binary_dirs 0) +ENDMACRO(INCLUDE_SUBDIRECTORY) + +######################################################################## +# Include subdirectories (different than add) +######################################################################## +INCLUDE_SUBDIRECTORY(ic_reg_maps) +INCLUDE_SUBDIRECTORY(types) +INCLUDE_SUBDIRECTORY(convert) +INCLUDE_SUBDIRECTORY(transport) +INCLUDE_SUBDIRECTORY(usrp) +INCLUDE_SUBDIRECTORY(utils) + +######################################################################## +# Setup UHD_VERSION_STRING for version.cpp +######################################################################## +CONFIGURE_FILE( + ${CMAKE_CURRENT_SOURCE_DIR}/version.cpp + ${CMAKE_CURRENT_BINARY_DIR}/version.cpp +@ONLY) + +######################################################################## +# Append to the list of sources for lib uhd +######################################################################## +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/deprecated.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/device.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exception.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/property_tree.cpp + ${CMAKE_CURRENT_BINARY_DIR}/version.cpp +) + +######################################################################## +# Setup libuhd library +######################################################################## +ADD_LIBRARY(uhd SHARED ${libuhd_sources}) +TARGET_LINK_LIBRARIES(uhd ${Boost_LIBRARIES} ${libuhd_libs}) +SET_TARGET_PROPERTIES(uhd PROPERTIES DEFINE_SYMBOL "UHD_DLL_EXPORTS") +SET_TARGET_PROPERTIES(uhd PROPERTIES SOVERSION "${UHD_VERSION_MAJOR}") +SET_TARGET_PROPERTIES(uhd PROPERTIES VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_MINOR}") +IF(DEFINED LIBUHD_OUTPUT_NAME) + SET_TARGET_PROPERTIES(uhd PROPERTIES OUTPUT_NAME ${LIBUHD_OUTPUT_NAME}) +ENDIF(DEFINED LIBUHD_OUTPUT_NAME) + +INSTALL(TARGETS uhd + LIBRARY DESTINATION ${LIBRARY_DIR} COMPONENT libraries # .so file + ARCHIVE DESTINATION ${LIBRARY_DIR} COMPONENT libraries # .lib file + RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT libraries # .dll file +) diff --git a/host/lib/convert/CMakeLists.txt b/host/lib/convert/CMakeLists.txt new file mode 100644 index 000000000..c42a0a434 --- /dev/null +++ b/host/lib/convert/CMakeLists.txt @@ -0,0 +1,120 @@ +# +# Copyright 2011-2012 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +INCLUDE(CheckIncludeFileCXX) +MESSAGE(STATUS "") + +######################################################################## +# Look for Orc support +######################################################################## +FIND_PACKAGE(PkgConfig) +IF(PKG_CONFIG_FOUND) +PKG_CHECK_MODULES(ORC "orc-0.4 > 0.4.11") +ENDIF(PKG_CONFIG_FOUND) + +FIND_PROGRAM(ORCC_EXECUTABLE orcc) + +LIBUHD_REGISTER_COMPONENT("ORC" ENABLE_ORC ON "ENABLE_LIBUHD;ORC_FOUND;ORCC_EXECUTABLE" OFF) + +IF(ENABLE_ORC) + INCLUDE_DIRECTORIES(${ORC_INCLUDE_DIRS}) + LINK_DIRECTORIES(${ORC_LIBRARY_DIRS}) + ENABLE_LANGUAGE(C) + + SET(orcc_src ${CMAKE_CURRENT_SOURCE_DIR}/convert_orc.orc) + + GET_FILENAME_COMPONENT(orc_file_name_we ${orcc_src} NAME_WE) + SET(orcc_gen ${CMAKE_CURRENT_BINARY_DIR}/${orc_file_name_we}.c) + MESSAGE(STATUS "Orc found, enabling Orc support.") + ADD_CUSTOM_COMMAND( + COMMAND ${ORCC_EXECUTABLE} --implementation -o ${orcc_gen} ${orcc_src} + DEPENDS ${orcc_src} OUTPUT ${orcc_gen} + ) + LIBUHD_APPEND_SOURCES(${orcc_gen}) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_orc.cpp + ) + LIBUHD_APPEND_LIBS(${ORC_LIBRARIES}) +ELSE(ENABLE_ORC) + MESSAGE(STATUS "Orc not found, disabling orc support.") +ENDIF(ENABLE_ORC) + +######################################################################## +# Check for SSE2 SIMD headers +######################################################################## +IF(CMAKE_COMPILER_IS_GNUCXX) + SET(EMMINTRIN_FLAGS -msse2) +ELSEIF(MSVC) + SET(EMMINTRIN_FLAGS /arch:SSE2) +ENDIF() + +SET(CMAKE_REQUIRED_FLAGS ${EMMINTRIN_FLAGS}) +CHECK_INCLUDE_FILE_CXX(emmintrin.h HAVE_EMMINTRIN_H) +UNSET(CMAKE_REQUIRED_FLAGS) + +IF(HAVE_EMMINTRIN_H) + SET(convert_with_sse2_sources + ${CMAKE_CURRENT_SOURCE_DIR}/convert_fc32_with_sse2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_fc64_with_sse2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_fc32_to_sc8_with_sse2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_fc64_to_sc8_with_sse2.cpp + ) + SET_SOURCE_FILES_PROPERTIES( + ${convert_with_sse2_sources} + PROPERTIES COMPILE_FLAGS "${EMMINTRIN_FLAGS}" + ) + LIBUHD_APPEND_SOURCES(${convert_with_sse2_sources}) +ENDIF(HAVE_EMMINTRIN_H) + +######################################################################## +# Check for NEON SIMD headers +######################################################################## +IF(CMAKE_COMPILER_IS_GNUCXX) + SET(NEON_FLAGS "-mfloat-abi=softfp -mfpu=neon") + SET(CMAKE_REQUIRED_FLAGS ${NEON_FLAGS}) + CHECK_INCLUDE_FILE_CXX(arm_neon.h HAVE_ARM_NEON_H) + UNSET(CMAKE_REQUIRED_FLAGS) +ENDIF(CMAKE_COMPILER_IS_GNUCXX) + +IF(HAVE_ARM_NEON_H) + SET_SOURCE_FILES_PROPERTIES( + ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_neon.cpp + PROPERTIES COMPILE_FLAGS "${NEON_FLAGS}" + ) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_neon.cpp + ) +ENDIF() + +######################################################################## +# Convert types generation +######################################################################## +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_convert_general.py + ${CMAKE_CURRENT_BINARY_DIR}/convert_general.cpp +) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_tables.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_impl.cpp +) diff --git a/host/lib/convert/convert_common.hpp b/host/lib/convert/convert_common.hpp new file mode 100644 index 000000000..55bc2e99d --- /dev/null +++ b/host/lib/convert/convert_common.hpp @@ -0,0 +1,241 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_CONVERT_COMMON_HPP +#define INCLUDED_LIBUHD_CONVERT_COMMON_HPP + +#include <uhd/convert.hpp> +#include <uhd/utils/static.hpp> +#include <boost/cstdint.hpp> +#include <complex> + +#define _DECLARE_CONVERTER(name, in_form, num_in, out_form, num_out, prio) \ + struct name : public uhd::convert::converter{ \ + static sptr make(void){return sptr(new name());} \ + double scale_factor; \ + void set_scalar(const double s){scale_factor = s;} \ + void operator()(const input_type&, const output_type&, const size_t); \ + }; \ + UHD_STATIC_BLOCK(__register_##name##_##prio){ \ + uhd::convert::id_type id; \ + id.input_format = #in_form; \ + id.num_inputs = num_in; \ + id.output_format = #out_form; \ + id.num_outputs = num_out; \ + uhd::convert::register_converter(id, &name::make, prio); \ + } \ + void name::operator()( \ + const input_type &inputs, const output_type &outputs, const size_t nsamps \ + ) + +#define DECLARE_CONVERTER(in_form, num_in, out_form, num_out, prio) \ + _DECLARE_CONVERTER(__convert_##in_form##_##num_in##_##out_form##_##num_out##_##prio, in_form, num_in, out_form, num_out, prio) + +/*********************************************************************** + * Setup priorities + **********************************************************************/ +static const int PRIORITY_GENERAL = 0; +static const int PRIORITY_EMPTY = -1; + +#ifdef __ARM_NEON__ +static const int PRIORITY_LIBORC = 3; +static const int PRIORITY_SIMD = 1; //neon conversions could be implemented better, orc wins +static const int PRIORITY_TABLE = 2; //tables require large cache, so they are slower on arm +#else +static const int PRIORITY_LIBORC = 1; +static const int PRIORITY_SIMD = 2; +static const int PRIORITY_TABLE = 3; +#endif + +/*********************************************************************** + * Typedefs + **********************************************************************/ +typedef std::complex<double> fc64_t; +typedef std::complex<float> fc32_t; +typedef std::complex<boost::int32_t> sc32_t; +typedef std::complex<boost::int16_t> sc16_t; +typedef std::complex<boost::int8_t> sc8_t; +typedef double f64_t; +typedef float f32_t; +typedef boost::int32_t s32_t; +typedef boost::int16_t s16_t; +typedef boost::int8_t s8_t; + +typedef boost::uint32_t item32_t; + +/*********************************************************************** + * Convert complex short buffer to items32 sc16 + **********************************************************************/ +static UHD_INLINE item32_t sc16_to_item32_sc16(sc16_t num, double){ + boost::uint16_t real = num.real(); + boost::uint16_t imag = num.imag(); + return (item32_t(real) << 16) | (item32_t(imag) << 0); +} + +/*********************************************************************** + * Convert items32 sc16 buffer to complex short + **********************************************************************/ +static UHD_INLINE sc16_t item32_sc16_to_sc16(item32_t item, double){ + return sc16_t( + boost::int16_t(item >> 16), + boost::int16_t(item >> 0) + ); +} + +/*********************************************************************** + * Convert complex float buffer to items32 sc16 + **********************************************************************/ +static UHD_INLINE item32_t fc32_to_item32_sc16(fc32_t num, double scale_factor){ + boost::uint16_t real = boost::int16_t(num.real()*float(scale_factor)); + boost::uint16_t imag = boost::int16_t(num.imag()*float(scale_factor)); + return (item32_t(real) << 16) | (item32_t(imag) << 0); +} + +/*********************************************************************** + * Convert items32 sc16 buffer to complex float + **********************************************************************/ +static UHD_INLINE fc32_t item32_sc16_to_fc32(item32_t item, double scale_factor){ + return fc32_t( + float(boost::int16_t(item >> 16)*float(scale_factor)), + float(boost::int16_t(item >> 0)*float(scale_factor)) + ); +} + +/*********************************************************************** + * Convert complex double buffer to items32 sc16 + **********************************************************************/ +static UHD_INLINE item32_t fc64_to_item32_sc16(fc64_t num, double scale_factor){ + boost::uint16_t real = boost::int16_t(num.real()*scale_factor); + boost::uint16_t imag = boost::int16_t(num.imag()*scale_factor); + return (item32_t(real) << 16) | (item32_t(imag) << 0); +} + +/*********************************************************************** + * Convert items32 sc16 buffer to complex double + **********************************************************************/ +static UHD_INLINE fc64_t item32_sc16_to_fc64(item32_t item, double scale_factor){ + return fc64_t( + float(boost::int16_t(item >> 16)*scale_factor), + float(boost::int16_t(item >> 0)*scale_factor) + ); +} + +/*********************************************************************** + * Convert items32 sc8 buffer to complex char + **********************************************************************/ +static UHD_INLINE void item32_sc8_to_sc8(item32_t item, sc8_t &out0, sc8_t &out1, double){ + out0 = sc8_t( + boost::int8_t(item >> 8), + boost::int8_t(item >> 0) + ); + out1 = sc8_t( + boost::int8_t(item >> 24), + boost::int8_t(item >> 16) + ); +} + +/*********************************************************************** + * Convert items32 sc8 buffer to complex short + **********************************************************************/ +static UHD_INLINE void item32_sc8_to_sc16(item32_t item, sc16_t &out0, sc16_t &out1, double){ + out0 = sc16_t( + boost::int8_t(item >> 8), + boost::int8_t(item >> 0) + ); + out1 = sc16_t( + boost::int8_t(item >> 24), + boost::int8_t(item >> 16) + ); +} + +/*********************************************************************** + * Convert items32 sc8 buffer to complex float + **********************************************************************/ +static UHD_INLINE void item32_sc8_to_fc32(item32_t item, fc32_t &out0, fc32_t &out1, double scale_factor){ + out0 = fc32_t( + float(boost::int8_t(item >> 8)*float(scale_factor)), + float(boost::int8_t(item >> 0)*float(scale_factor)) + ); + out1 = fc32_t( + float(boost::int8_t(item >> 24)*float(scale_factor)), + float(boost::int8_t(item >> 16)*float(scale_factor)) + ); +} + +/*********************************************************************** + * Convert items32 sc8 buffer to complex double + **********************************************************************/ +static UHD_INLINE void item32_sc8_to_fc64(item32_t item, fc64_t &out0, fc64_t &out1, double scale_factor){ + out0 = fc64_t( + float(boost::int8_t(item >> 8)*scale_factor), + float(boost::int8_t(item >> 0)*scale_factor) + ); + out1 = fc64_t( + float(boost::int8_t(item >> 24)*scale_factor), + float(boost::int8_t(item >> 16)*scale_factor) + ); +} + +/*********************************************************************** + * Convert complex char to items32 sc8 buffer + **********************************************************************/ +static UHD_INLINE item32_t sc8_to_item32_sc8(sc8_t in0, sc8_t in1, double){ + return + (item32_t(boost::uint8_t(in0.real())) << 8) | + (item32_t(boost::uint8_t(in0.imag())) << 0) | + (item32_t(boost::uint8_t(in1.real())) << 24) | + (item32_t(boost::uint8_t(in1.imag())) << 16) + ; +} + +/*********************************************************************** + * Convert complex short to items32 sc8 buffer + **********************************************************************/ +static UHD_INLINE item32_t sc16_to_item32_sc8(sc16_t in0, sc16_t in1, double){ + return + (item32_t(boost::uint8_t(in0.real())) << 8) | + (item32_t(boost::uint8_t(in0.imag())) << 0) | + (item32_t(boost::uint8_t(in1.real())) << 24) | + (item32_t(boost::uint8_t(in1.imag())) << 16) + ; +} + +/*********************************************************************** + * Convert complex float to items32 sc8 buffer + **********************************************************************/ +static UHD_INLINE item32_t fc32_to_item32_sc8(fc32_t in0, fc32_t in1, double scale_factor){ + return + (item32_t(boost::uint8_t(in0.real()*float(scale_factor))) << 8) | + (item32_t(boost::uint8_t(in0.imag()*float(scale_factor))) << 0) | + (item32_t(boost::uint8_t(in1.real()*float(scale_factor))) << 24) | + (item32_t(boost::uint8_t(in1.imag()*float(scale_factor))) << 16) + ; +} + +/*********************************************************************** + * Convert complex double to items32 sc8 buffer + **********************************************************************/ +static UHD_INLINE item32_t fc64_to_item32_sc8(fc64_t in0, fc64_t in1, double scale_factor){ + return + (item32_t(boost::uint8_t(in0.real()*(scale_factor))) << 8) | + (item32_t(boost::uint8_t(in0.imag()*(scale_factor))) << 0) | + (item32_t(boost::uint8_t(in1.real()*(scale_factor))) << 24) | + (item32_t(boost::uint8_t(in1.imag()*(scale_factor))) << 16) + ; +} + +#endif /* INCLUDED_LIBUHD_CONVERT_COMMON_HPP */ diff --git a/host/lib/convert/convert_fc32_to_sc8_with_sse2.cpp b/host/lib/convert/convert_fc32_to_sc8_with_sse2.cpp new file mode 100644 index 000000000..b633f487c --- /dev/null +++ b/host/lib/convert/convert_fc32_to_sc8_with_sse2.cpp @@ -0,0 +1,150 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> +#include <emmintrin.h> + +using namespace uhd::convert; + +UHD_INLINE __m128i pack_sc32_4x_be( + const __m128 &in0, const __m128 &in1, + const __m128 &in2, const __m128 &in3, + const __m128 &scalar +){ + __m128i tmpi0 = _mm_cvtps_epi32(_mm_mul_ps(in0, scalar)); + tmpi0 = _mm_shuffle_epi32(tmpi0, _MM_SHUFFLE(1, 0, 3, 2)); + __m128i tmpi1 = _mm_cvtps_epi32(_mm_mul_ps(in1, scalar)); + tmpi1 = _mm_shuffle_epi32(tmpi1, _MM_SHUFFLE(1, 0, 3, 2)); + const __m128i lo = _mm_packs_epi32(tmpi0, tmpi1); + + __m128i tmpi2 = _mm_cvtps_epi32(_mm_mul_ps(in2, scalar)); + tmpi2 = _mm_shuffle_epi32(tmpi2, _MM_SHUFFLE(1, 0, 3, 2)); + __m128i tmpi3 = _mm_cvtps_epi32(_mm_mul_ps(in3, scalar)); + tmpi3 = _mm_shuffle_epi32(tmpi3, _MM_SHUFFLE(1, 0, 3, 2)); + const __m128i hi = _mm_packs_epi32(tmpi2, tmpi3); + + return _mm_packs_epi16(lo, hi); +} + +UHD_INLINE __m128i pack_sc32_4x_le( + const __m128 &in0, const __m128 &in1, + const __m128 &in2, const __m128 &in3, + const __m128 &scalar +){ + __m128i tmpi0 = _mm_cvtps_epi32(_mm_mul_ps(in0, scalar)); + tmpi0 = _mm_shuffle_epi32(tmpi0, _MM_SHUFFLE(2, 3, 0, 1)); + __m128i tmpi1 = _mm_cvtps_epi32(_mm_mul_ps(in1, scalar)); + tmpi1 = _mm_shuffle_epi32(tmpi1, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i lo = _mm_packs_epi32(tmpi0, tmpi1); + + __m128i tmpi2 = _mm_cvtps_epi32(_mm_mul_ps(in2, scalar)); + tmpi2 = _mm_shuffle_epi32(tmpi2, _MM_SHUFFLE(2, 3, 0, 1)); + __m128i tmpi3 = _mm_cvtps_epi32(_mm_mul_ps(in3, scalar)); + tmpi3 = _mm_shuffle_epi32(tmpi3, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i hi = _mm_packs_epi32(tmpi2, tmpi3); + + return _mm_packs_epi16(lo, hi); +} + +DECLARE_CONVERTER(fc32, 1, sc8_item32_be, 1, PRIORITY_SIMD){ + const fc32_t *input = reinterpret_cast<const fc32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const __m128 scalar = _mm_set_ps1(float(scale_factor)); + + #define convert_fc32_1_to_sc8_item32_1_bswap_guts(_al_) \ + for (size_t j = 0; i+7 < nsamps; i+=8, j+=4){ \ + /* load from input */ \ + __m128 tmp0 = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+0)); \ + __m128 tmp1 = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+2)); \ + __m128 tmp2 = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+4)); \ + __m128 tmp3 = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+6)); \ + \ + /* convert */ \ + const __m128i tmpi = pack_sc32_4x_be(tmp0, tmp1, tmp2, tmp3, scalar); \ + \ + /* store to output */ \ + _mm_storeu_si128(reinterpret_cast<__m128i *>(output+j), tmpi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + if ((size_t(input) & 0xf) == 0){ + convert_fc32_1_to_sc8_item32_1_bswap_guts(_) + } + else{ + convert_fc32_1_to_sc8_item32_1_bswap_guts(u_) + } + + //convert remainder + const size_t num_pairs = nsamps/2; + for (size_t j = i/2; j < num_pairs; j++, i+=2){ + const item32_t item = fc32_to_item32_sc8(input[i], input[i+1], scale_factor); + output[j] = uhd::byteswap(item); + } + + if (nsamps != num_pairs*2){ + const item32_t item = fc32_to_item32_sc8(input[nsamps-1], 0, scale_factor); + output[num_pairs] = uhd::byteswap(item); + } +} + +DECLARE_CONVERTER(fc32, 1, sc8_item32_le, 1, PRIORITY_SIMD){ + const fc32_t *input = reinterpret_cast<const fc32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const __m128 scalar = _mm_set_ps1(float(scale_factor)); + + #define convert_fc32_1_to_sc8_item32_1_nswap_guts(_al_) \ + for (size_t j = 0; i+7 < nsamps; i+=8, j+=4){ \ + /* load from input */ \ + __m128 tmp0 = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+0)); \ + __m128 tmp1 = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+2)); \ + __m128 tmp2 = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+4)); \ + __m128 tmp3 = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+6)); \ + \ + /* convert */ \ + const __m128i tmpi = pack_sc32_4x_le(tmp0, tmp1, tmp2, tmp3, scalar); \ + \ + /* store to output */ \ + _mm_storeu_si128(reinterpret_cast<__m128i *>(output+j), tmpi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + if ((size_t(input) & 0xf) == 0){ + convert_fc32_1_to_sc8_item32_1_nswap_guts(_) + } + else{ + convert_fc32_1_to_sc8_item32_1_nswap_guts(u_) + } + + //convert remainder + const size_t num_pairs = nsamps/2; + for (size_t j = i/2; j < num_pairs; j++, i+=2){ + const item32_t item = fc32_to_item32_sc8(input[i], input[i+1], scale_factor); + output[j] = (item); + } + + if (nsamps != num_pairs*2){ + const item32_t item = fc32_to_item32_sc8(input[nsamps-1], 0, scale_factor); + output[num_pairs] = (item); + } +} diff --git a/host/lib/convert/convert_fc32_with_sse2.cpp b/host/lib/convert/convert_fc32_with_sse2.cpp new file mode 100644 index 000000000..97a3e8cdc --- /dev/null +++ b/host/lib/convert/convert_fc32_with_sse2.cpp @@ -0,0 +1,196 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> +#include <emmintrin.h> + +using namespace uhd::convert; + +DECLARE_CONVERTER(fc32, 1, sc16_item32_le, 1, PRIORITY_SIMD){ + const fc32_t *input = reinterpret_cast<const fc32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const __m128 scalar = _mm_set_ps1(float(scale_factor)); + + #define convert_fc32_1_to_item32_1_nswap_guts(_al_) \ + for (; i+3 < nsamps; i+=4){ \ + /* load from input */ \ + __m128 tmplo = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+0)); \ + __m128 tmphi = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+2)); \ + \ + /* convert and scale */ \ + __m128i tmpilo = _mm_cvtps_epi32(_mm_mul_ps(tmplo, scalar)); \ + __m128i tmpihi = _mm_cvtps_epi32(_mm_mul_ps(tmphi, scalar)); \ + \ + /* pack + swap 16-bit pairs */ \ + __m128i tmpi = _mm_packs_epi32(tmpilo, tmpihi); \ + tmpi = _mm_shufflelo_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); \ + tmpi = _mm_shufflehi_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); \ + \ + /* store to output */ \ + _mm_storeu_si128(reinterpret_cast<__m128i *>(output+i), tmpi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + switch (size_t(input) & 0xf){ + case 0x8: + output[i] = fc32_to_item32_sc16(input[i], float(scale_factor)); i++; + case 0x0: + convert_fc32_1_to_item32_1_nswap_guts(_) + break; + default: convert_fc32_1_to_item32_1_nswap_guts(u_) + } + + //convert remainder + for (; i < nsamps; i++){ + output[i] = fc32_to_item32_sc16(input[i], float(scale_factor)); + } +} + +DECLARE_CONVERTER(fc32, 1, sc16_item32_be, 1, PRIORITY_SIMD){ + const fc32_t *input = reinterpret_cast<const fc32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const __m128 scalar = _mm_set_ps1(float(scale_factor)); + + #define convert_fc32_1_to_item32_1_bswap_guts(_al_) \ + for (; i+3 < nsamps; i+=4){ \ + /* load from input */ \ + __m128 tmplo = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+0)); \ + __m128 tmphi = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+2)); \ + \ + /* convert and scale */ \ + __m128i tmpilo = _mm_cvtps_epi32(_mm_mul_ps(tmplo, scalar)); \ + __m128i tmpihi = _mm_cvtps_epi32(_mm_mul_ps(tmphi, scalar)); \ + \ + /* pack + byteswap -> byteswap 16 bit words */ \ + __m128i tmpi = _mm_packs_epi32(tmpilo, tmpihi); \ + tmpi = _mm_or_si128(_mm_srli_epi16(tmpi, 8), _mm_slli_epi16(tmpi, 8)); \ + \ + /* store to output */ \ + _mm_storeu_si128(reinterpret_cast<__m128i *>(output+i), tmpi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + switch (size_t(input) & 0xf){ + case 0x8: + output[i] = uhd::byteswap(fc32_to_item32_sc16(input[i], float(scale_factor))); i++; + case 0x0: + convert_fc32_1_to_item32_1_bswap_guts(_) + break; + default: convert_fc32_1_to_item32_1_bswap_guts(u_) + } + + //convert remainder + for (; i < nsamps; i++){ + output[i] = uhd::byteswap(fc32_to_item32_sc16(input[i], float(scale_factor))); + } +} + +DECLARE_CONVERTER(sc16_item32_le, 1, fc32, 1, PRIORITY_SIMD){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + fc32_t *output = reinterpret_cast<fc32_t *>(outputs[0]); + + const __m128 scalar = _mm_set_ps1(float(scale_factor)/(1 << 16)); + const __m128i zeroi = _mm_setzero_si128(); + + #define convert_item32_1_to_fc32_1_nswap_guts(_al_) \ + for (; i+3 < nsamps; i+=4){ \ + /* load from input */ \ + __m128i tmpi = _mm_loadu_si128(reinterpret_cast<const __m128i *>(input+i)); \ + \ + /* unpack + swap 16-bit pairs */ \ + tmpi = _mm_shufflelo_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); \ + tmpi = _mm_shufflehi_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); \ + __m128i tmpilo = _mm_unpacklo_epi16(zeroi, tmpi); /* value in upper 16 bits */ \ + __m128i tmpihi = _mm_unpackhi_epi16(zeroi, tmpi); \ + \ + /* convert and scale */ \ + __m128 tmplo = _mm_mul_ps(_mm_cvtepi32_ps(tmpilo), scalar); \ + __m128 tmphi = _mm_mul_ps(_mm_cvtepi32_ps(tmpihi), scalar); \ + \ + /* store to output */ \ + _mm_store ## _al_ ## ps(reinterpret_cast<float *>(output+i+0), tmplo); \ + _mm_store ## _al_ ## ps(reinterpret_cast<float *>(output+i+2), tmphi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + switch (size_t(output) & 0xf){ + case 0x8: + output[i] = item32_sc16_to_fc32(input[i], float(scale_factor)); i++; + case 0x0: + convert_item32_1_to_fc32_1_nswap_guts(_) + break; + default: convert_item32_1_to_fc32_1_nswap_guts(u_) + } + + //convert remainder + for (; i < nsamps; i++){ + output[i] = item32_sc16_to_fc32(input[i], float(scale_factor)); + } +} + +DECLARE_CONVERTER(sc16_item32_be, 1, fc32, 1, PRIORITY_SIMD){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + fc32_t *output = reinterpret_cast<fc32_t *>(outputs[0]); + + const __m128 scalar = _mm_set_ps1(float(scale_factor)/(1 << 16)); + const __m128i zeroi = _mm_setzero_si128(); + + #define convert_item32_1_to_fc32_1_bswap_guts(_al_) \ + for (; i+3 < nsamps; i+=4){ \ + /* load from input */ \ + __m128i tmpi = _mm_loadu_si128(reinterpret_cast<const __m128i *>(input+i)); \ + \ + /* byteswap + unpack -> byteswap 16 bit words */ \ + tmpi = _mm_or_si128(_mm_srli_epi16(tmpi, 8), _mm_slli_epi16(tmpi, 8)); \ + __m128i tmpilo = _mm_unpacklo_epi16(zeroi, tmpi); /* value in upper 16 bits */ \ + __m128i tmpihi = _mm_unpackhi_epi16(zeroi, tmpi); \ + \ + /* convert and scale */ \ + __m128 tmplo = _mm_mul_ps(_mm_cvtepi32_ps(tmpilo), scalar); \ + __m128 tmphi = _mm_mul_ps(_mm_cvtepi32_ps(tmpihi), scalar); \ + \ + /* store to output */ \ + _mm_store ## _al_ ## ps(reinterpret_cast<float *>(output+i+0), tmplo); \ + _mm_store ## _al_ ## ps(reinterpret_cast<float *>(output+i+2), tmphi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + switch (size_t(output) & 0xf){ + case 0x8: + output[i] = item32_sc16_to_fc32(uhd::byteswap(input[i]), float(scale_factor)); i++; + case 0x0: + convert_item32_1_to_fc32_1_bswap_guts(_) + break; + default: convert_item32_1_to_fc32_1_bswap_guts(u_) + } + + //convert remainder + for (; i < nsamps; i++){ + output[i] = item32_sc16_to_fc32(uhd::byteswap(input[i]), float(scale_factor)); + } +} diff --git a/host/lib/convert/convert_fc64_to_sc8_with_sse2.cpp b/host/lib/convert/convert_fc64_to_sc8_with_sse2.cpp new file mode 100644 index 000000000..405850601 --- /dev/null +++ b/host/lib/convert/convert_fc64_to_sc8_with_sse2.cpp @@ -0,0 +1,156 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> +#include <emmintrin.h> + +using namespace uhd::convert; + +UHD_INLINE __m128i pack_sc8_item32_4x( + const __m128i &in0, const __m128i &in1, + const __m128i &in2, const __m128i &in3 +){ + const __m128i lo = _mm_packs_epi32(in0, in1); + const __m128i hi = _mm_packs_epi32(in2, in3); + return _mm_packs_epi16(lo, hi); +} + +UHD_INLINE __m128i pack_sc32_4x_be( + const __m128d &lo, const __m128d &hi, + const __m128d &scalar +){ + const __m128i tmpi_lo = _mm_cvttpd_epi32(_mm_mul_pd(hi, scalar)); + const __m128i tmpi_hi = _mm_cvttpd_epi32(_mm_mul_pd(lo, scalar)); + return _mm_unpacklo_epi64(tmpi_lo, tmpi_hi); +} + +UHD_INLINE __m128i pack_sc32_4x_le( + const __m128d &lo, const __m128d &hi, + const __m128d &scalar +){ + const __m128i tmpi_lo = _mm_cvttpd_epi32(_mm_mul_pd(lo, scalar)); + const __m128i tmpi_hi = _mm_cvttpd_epi32(_mm_mul_pd(hi, scalar)); + const __m128i tmpi = _mm_unpacklo_epi64(tmpi_lo, tmpi_hi); + return _mm_shuffle_epi32(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); +} + +DECLARE_CONVERTER(fc64, 1, sc8_item32_be, 1, PRIORITY_SIMD){ + const fc64_t *input = reinterpret_cast<const fc64_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const __m128d scalar = _mm_set1_pd(scale_factor); + + #define convert_fc64_1_to_sc8_item32_1_bswap_guts(_al_) \ + for (size_t j = 0; i+7 < nsamps; i+=8, j+=4){ \ + /* load from input */ \ + __m128d tmp0 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+0)); \ + __m128d tmp1 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+1)); \ + __m128d tmp2 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+2)); \ + __m128d tmp3 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+3)); \ + __m128d tmp4 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+4)); \ + __m128d tmp5 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+5)); \ + __m128d tmp6 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+6)); \ + __m128d tmp7 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+7)); \ + \ + /* interleave */ \ + const __m128i tmpi = pack_sc8_item32_4x( \ + pack_sc32_4x_be(tmp0, tmp1, scalar), \ + pack_sc32_4x_be(tmp2, tmp3, scalar), \ + pack_sc32_4x_be(tmp4, tmp5, scalar), \ + pack_sc32_4x_be(tmp6, tmp7, scalar) \ + ); \ + \ + /* store to output */ \ + _mm_storeu_si128(reinterpret_cast<__m128i *>(output+j), tmpi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + if ((size_t(input) & 0xf) == 0){ + convert_fc64_1_to_sc8_item32_1_bswap_guts(_) + } + else{ + convert_fc64_1_to_sc8_item32_1_bswap_guts(u_) + } + + //convert remainder + const size_t num_pairs = nsamps/2; + for (size_t j = i/2; j < num_pairs; j++, i+=2){ + const item32_t item = fc64_to_item32_sc8(input[i], input[i+1], scale_factor); + output[j] = uhd::byteswap(item); + } + + if (nsamps != num_pairs*2){ + const item32_t item = fc64_to_item32_sc8(input[nsamps-1], 0, scale_factor); + output[num_pairs] = uhd::byteswap(item); + } +} + +DECLARE_CONVERTER(fc64, 1, sc8_item32_le, 1, PRIORITY_SIMD){ + const fc64_t *input = reinterpret_cast<const fc64_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const __m128d scalar = _mm_set1_pd(scale_factor); + + #define convert_fc64_1_to_sc8_item32_1_nswap_guts(_al_) \ + for (size_t j = 0; i+7 < nsamps; i+=8, j+=4){ \ + /* load from input */ \ + __m128d tmp0 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+0)); \ + __m128d tmp1 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+1)); \ + __m128d tmp2 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+2)); \ + __m128d tmp3 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+3)); \ + __m128d tmp4 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+4)); \ + __m128d tmp5 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+5)); \ + __m128d tmp6 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+6)); \ + __m128d tmp7 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+7)); \ + \ + /* interleave */ \ + const __m128i tmpi = pack_sc8_item32_4x( \ + pack_sc32_4x_le(tmp0, tmp1, scalar), \ + pack_sc32_4x_le(tmp2, tmp3, scalar), \ + pack_sc32_4x_le(tmp4, tmp5, scalar), \ + pack_sc32_4x_le(tmp6, tmp7, scalar) \ + ); \ + \ + /* store to output */ \ + _mm_storeu_si128(reinterpret_cast<__m128i *>(output+j), tmpi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + if ((size_t(input) & 0xf) == 0){ + convert_fc64_1_to_sc8_item32_1_nswap_guts(_) + } + else{ + convert_fc64_1_to_sc8_item32_1_nswap_guts(u_) + } + + //convert remainder + const size_t num_pairs = nsamps/2; + for (size_t j = i/2; j < num_pairs; j++, i+=2){ + const item32_t item = fc64_to_item32_sc8(input[i], input[i+1], scale_factor); + output[j] = (item); + } + + if (nsamps != num_pairs*2){ + const item32_t item = fc64_to_item32_sc8(input[nsamps-1], 0, scale_factor); + output[num_pairs] = (item); + } +} diff --git a/host/lib/convert/convert_fc64_with_sse2.cpp b/host/lib/convert/convert_fc64_with_sse2.cpp new file mode 100644 index 000000000..6e097e380 --- /dev/null +++ b/host/lib/convert/convert_fc64_with_sse2.cpp @@ -0,0 +1,212 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> +#include <emmintrin.h> + +using namespace uhd::convert; + +DECLARE_CONVERTER(fc64, 1, sc16_item32_le, 1, PRIORITY_SIMD){ + const fc64_t *input = reinterpret_cast<const fc64_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const __m128d scalar = _mm_set1_pd(scale_factor); + + #define convert_fc64_1_to_item32_1_nswap_guts(_al_) \ + for (; i+3 < nsamps; i+=4){ \ + /* load from input */ \ + __m128d tmp0 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+0)); \ + __m128d tmp1 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+1)); \ + __m128d tmp2 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+2)); \ + __m128d tmp3 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+3)); \ + \ + /* convert and scale */ \ + __m128i tmpi0 = _mm_cvttpd_epi32(_mm_mul_pd(tmp0, scalar)); \ + __m128i tmpi1 = _mm_cvttpd_epi32(_mm_mul_pd(tmp1, scalar)); \ + __m128i tmpilo = _mm_unpacklo_epi64(tmpi0, tmpi1); \ + __m128i tmpi2 = _mm_cvttpd_epi32(_mm_mul_pd(tmp2, scalar)); \ + __m128i tmpi3 = _mm_cvttpd_epi32(_mm_mul_pd(tmp3, scalar)); \ + __m128i tmpihi = _mm_unpacklo_epi64(tmpi2, tmpi3); \ + \ + /* pack + swap 16-bit pairs */ \ + __m128i tmpi = _mm_packs_epi32(tmpilo, tmpihi); \ + tmpi = _mm_shufflelo_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); \ + tmpi = _mm_shufflehi_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); \ + \ + /* store to output */ \ + _mm_storeu_si128(reinterpret_cast<__m128i *>(output+i), tmpi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + if ((size_t(input) & 0xf) == 0){ + convert_fc64_1_to_item32_1_nswap_guts(_) + } + else{ + convert_fc64_1_to_item32_1_nswap_guts(u_) + } + + //convert remainder + for (; i < nsamps; i++){ + output[i] = fc64_to_item32_sc16(input[i], scale_factor); + } +} + +DECLARE_CONVERTER(fc64, 1, sc16_item32_be, 1, PRIORITY_SIMD){ + const fc64_t *input = reinterpret_cast<const fc64_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const __m128d scalar = _mm_set1_pd(scale_factor); + + #define convert_fc64_1_to_item32_1_bswap_guts(_al_) \ + for (; i+3 < nsamps; i+=4){ \ + /* load from input */ \ + __m128d tmp0 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+0)); \ + __m128d tmp1 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+1)); \ + __m128d tmp2 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+2)); \ + __m128d tmp3 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+3)); \ + \ + /* convert and scale */ \ + __m128i tmpi0 = _mm_cvttpd_epi32(_mm_mul_pd(tmp0, scalar)); \ + __m128i tmpi1 = _mm_cvttpd_epi32(_mm_mul_pd(tmp1, scalar)); \ + __m128i tmpilo = _mm_unpacklo_epi64(tmpi0, tmpi1); \ + __m128i tmpi2 = _mm_cvttpd_epi32(_mm_mul_pd(tmp2, scalar)); \ + __m128i tmpi3 = _mm_cvttpd_epi32(_mm_mul_pd(tmp3, scalar)); \ + __m128i tmpihi = _mm_unpacklo_epi64(tmpi2, tmpi3); \ + \ + /* pack + byteswap -> byteswap 16 bit words */ \ + __m128i tmpi = _mm_packs_epi32(tmpilo, tmpihi); \ + tmpi = _mm_or_si128(_mm_srli_epi16(tmpi, 8), _mm_slli_epi16(tmpi, 8)); \ + \ + /* store to output */ \ + _mm_storeu_si128(reinterpret_cast<__m128i *>(output+i), tmpi); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + if ((size_t(input) & 0xf) == 0){ + convert_fc64_1_to_item32_1_bswap_guts(_) + } + else{ + convert_fc64_1_to_item32_1_bswap_guts(u_) + } + + //convert remainder + for (; i < nsamps; i++){ + output[i] = uhd::byteswap(fc64_to_item32_sc16(input[i], scale_factor)); + } +} + +DECLARE_CONVERTER(sc16_item32_le, 1, fc64, 1, PRIORITY_SIMD){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + fc64_t *output = reinterpret_cast<fc64_t *>(outputs[0]); + + const __m128d scalar = _mm_set1_pd(scale_factor/(1 << 16)); + const __m128i zeroi = _mm_setzero_si128(); + + #define convert_item32_1_to_fc64_1_nswap_guts(_al_) \ + for (; i+3 < nsamps; i+=4){ \ + /* load from input */ \ + __m128i tmpi = _mm_loadu_si128(reinterpret_cast<const __m128i *>(input+i)); \ + \ + /* unpack + swap 16-bit pairs */ \ + tmpi = _mm_shufflelo_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); \ + tmpi = _mm_shufflehi_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1)); \ + __m128i tmpilo = _mm_unpacklo_epi16(zeroi, tmpi); /* value in upper 16 bits */ \ + __m128i tmpihi = _mm_unpackhi_epi16(zeroi, tmpi); \ + \ + /* convert and scale */ \ + __m128d tmp0 = _mm_mul_pd(_mm_cvtepi32_pd(tmpilo), scalar); \ + tmpilo = _mm_unpackhi_epi64(tmpilo, zeroi); \ + __m128d tmp1 = _mm_mul_pd(_mm_cvtepi32_pd(tmpilo), scalar); \ + __m128d tmp2 = _mm_mul_pd(_mm_cvtepi32_pd(tmpihi), scalar); \ + tmpihi = _mm_unpackhi_epi64(tmpihi, zeroi); \ + __m128d tmp3 = _mm_mul_pd(_mm_cvtepi32_pd(tmpihi), scalar); \ + \ + /* store to output */ \ + _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+0), tmp0); \ + _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+1), tmp1); \ + _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+2), tmp2); \ + _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+3), tmp3); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + if ((size_t(output) & 0xf) == 0){ + convert_item32_1_to_fc64_1_nswap_guts(_) + } + else{ + convert_item32_1_to_fc64_1_nswap_guts(u_) + } + + //convert remainder + for (; i < nsamps; i++){ + output[i] = item32_sc16_to_fc64(input[i], scale_factor); + } +} + +DECLARE_CONVERTER(sc16_item32_be, 1, fc64, 1, PRIORITY_SIMD){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + fc64_t *output = reinterpret_cast<fc64_t *>(outputs[0]); + + const __m128d scalar = _mm_set1_pd(scale_factor/(1 << 16)); + const __m128i zeroi = _mm_setzero_si128(); + + #define convert_item32_1_to_fc64_1_bswap_guts(_al_) \ + for (; i+3 < nsamps; i+=4){ \ + /* load from input */ \ + __m128i tmpi = _mm_loadu_si128(reinterpret_cast<const __m128i *>(input+i)); \ + \ + /* byteswap + unpack -> byteswap 16 bit words */ \ + tmpi = _mm_or_si128(_mm_srli_epi16(tmpi, 8), _mm_slli_epi16(tmpi, 8)); \ + __m128i tmpilo = _mm_unpacklo_epi16(zeroi, tmpi); /* value in upper 16 bits */ \ + __m128i tmpihi = _mm_unpackhi_epi16(zeroi, tmpi); \ + \ + /* convert and scale */ \ + __m128d tmp0 = _mm_mul_pd(_mm_cvtepi32_pd(tmpilo), scalar); \ + tmpilo = _mm_unpackhi_epi64(tmpilo, zeroi); \ + __m128d tmp1 = _mm_mul_pd(_mm_cvtepi32_pd(tmpilo), scalar); \ + __m128d tmp2 = _mm_mul_pd(_mm_cvtepi32_pd(tmpihi), scalar); \ + tmpihi = _mm_unpackhi_epi64(tmpihi, zeroi); \ + __m128d tmp3 = _mm_mul_pd(_mm_cvtepi32_pd(tmpihi), scalar); \ + \ + /* store to output */ \ + _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+0), tmp0); \ + _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+1), tmp1); \ + _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+2), tmp2); \ + _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+3), tmp3); \ + } \ + + size_t i = 0; + + //dispatch according to alignment + if ((size_t(output) & 0xf) == 0){ + convert_item32_1_to_fc64_1_bswap_guts(_) + } + else{ + convert_item32_1_to_fc64_1_bswap_guts(u_) + } + + //convert remainder + for (; i < nsamps; i++){ + output[i] = item32_sc16_to_fc64(uhd::byteswap(input[i]), scale_factor); + } +} diff --git a/host/lib/convert/convert_impl.cpp b/host/lib/convert/convert_impl.cpp new file mode 100644 index 000000000..12ad54486 --- /dev/null +++ b/host/lib/convert/convert_impl.cpp @@ -0,0 +1,143 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/convert.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <complex> + +using namespace uhd; + +bool convert::operator==(const convert::id_type &lhs, const convert::id_type &rhs){ + return true + and (lhs.input_format == rhs.input_format) + and (lhs.num_inputs == rhs.num_inputs) + and (lhs.output_format == rhs.output_format) + and (lhs.num_outputs == rhs.num_outputs) + ; +} + +std::string convert::id_type::to_pp_string(void) const{ + return str(boost::format( + "conversion ID\n" + " Input format: %s\n" + " Num inputs: %d\n" + " Output format: %s\n" + " Num outputs: %d\n" + ) + % this->input_format + % this->num_inputs + % this->output_format + % this->num_outputs + ); +} + +/*********************************************************************** + * Define types for the function tables + **********************************************************************/ +struct fcn_table_entry_type{ + convert::priority_type prio; + convert::function_type fcn; +}; + +/*********************************************************************** + * Setup the table registry + **********************************************************************/ +typedef uhd::dict<convert::id_type, fcn_table_entry_type> fcn_table_type; +UHD_SINGLETON_FCN(fcn_table_type, get_table); + +/*********************************************************************** + * The registry functions + **********************************************************************/ +void uhd::convert::register_converter( + const id_type &id, + const function_type &fcn, + const priority_type prio +){ + //get a reference to the function table + fcn_table_type &table = get_table(); + + //register the function if higher priority + if (not table.has_key(id) or table[id].prio < prio){ + table[id].fcn = fcn; + table[id].prio = prio; + } + + //----------------------------------------------------------------// + UHD_LOGV(always) << "register_converter: " << id.to_pp_string() << std::endl + << " prio: " << prio << std::endl + << std::endl + ; + //----------------------------------------------------------------// +} + +/*********************************************************************** + * The converter functions + **********************************************************************/ +convert::function_type convert::get_converter(const id_type &id){ + if (get_table().has_key(id)) return get_table()[id].fcn; + throw uhd::key_error("Cannot find a conversion routine for " + id.to_pp_string()); +} + +/*********************************************************************** + * Mappings for item format to byte size for all items we can + **********************************************************************/ +typedef uhd::dict<std::string, size_t> item_size_type; +UHD_SINGLETON_FCN(item_size_type, get_item_size_table); + +void convert::register_bytes_per_item( + const std::string &format, const size_t size +){ + get_item_size_table()[format] = size; +} + +size_t convert::get_bytes_per_item(const std::string &format){ + if (get_item_size_table().has_key(format)) return get_item_size_table()[format]; + + //OK. I am sorry about this. + //We didnt find a match, so lets find a match for the first term. + //This is partially a hack because of the way I append strings. + //But as long as life is kind, we can keep this. + const size_t pos = format.find("_"); + if (pos != std::string::npos){ + return get_bytes_per_item(format.substr(0, pos)); + } + + throw uhd::key_error("Cannot find an item size:\n" + format); +} + +UHD_STATIC_BLOCK(convert_register_item_sizes){ + //register standard complex types + convert::register_bytes_per_item("fc64", sizeof(std::complex<double>)); + convert::register_bytes_per_item("fc32", sizeof(std::complex<float>)); + convert::register_bytes_per_item("sc64", sizeof(std::complex<boost::int64_t>)); + convert::register_bytes_per_item("sc32", sizeof(std::complex<boost::int32_t>)); + convert::register_bytes_per_item("sc16", sizeof(std::complex<boost::int16_t>)); + convert::register_bytes_per_item("sc8", sizeof(std::complex<boost::int8_t>)); + + //register standard real types + convert::register_bytes_per_item("f64", sizeof(double)); + convert::register_bytes_per_item("f32", sizeof(float)); + convert::register_bytes_per_item("s64", sizeof(boost::int64_t)); + convert::register_bytes_per_item("s32", sizeof(boost::int32_t)); + convert::register_bytes_per_item("s16", sizeof(boost::int16_t)); + convert::register_bytes_per_item("s8", sizeof(boost::int8_t)); +} diff --git a/host/lib/convert/convert_orc.orc b/host/lib/convert/convert_orc.orc new file mode 100644 index 000000000..f7075606e --- /dev/null +++ b/host/lib/convert/convert_orc.orc @@ -0,0 +1,80 @@ +.function _convert_fc32_1_to_item32_1_nswap_orc +.source 8 src +.dest 4 dst +.floatparam 4 scalar +.temp 8 scaled +.temp 8 converted +.temp 4 short +x2 mulf scaled, src, scalar +x2 convfl converted, scaled +x2 convlw short, converted +swapl short, short +x2 swapw dst, short + +.function _convert_fc32_1_to_item32_1_bswap_orc +.source 8 src +.dest 4 dst +.floatparam 4 scalar +.temp 8 scaled +.temp 8 converted +.temp 4 short +x2 mulf scaled, src, scalar +x2 convfl converted, scaled +x2 convlw short, converted +x2 swapw dst, short + +.function _convert_item32_1_to_fc32_1_nswap_orc +.source 4 src +.dest 8 dst +.floatparam 4 scalar +.temp 4 tmp1 +.temp 8 tmp2 +x2 swapw tmp1, src +swapl tmp1, tmp1 +x2 convswl tmp2, tmp1 +x2 convlf tmp2, tmp2 +x2 mulf dst, tmp2, scalar + +.function _convert_item32_1_to_fc32_1_bswap_orc +.source 4 src +.dest 8 dst +.floatparam 4 scalar +.temp 4 tmp1 +.temp 8 tmp2 +x2 swapw tmp1, src +x2 convswl tmp2, tmp1 +x2 convlf tmp2, tmp2 +x2 mulf dst, tmp2, scalar + +.function _convert_sc16_1_to_item32_1_nswap_orc +.source 4 src +.dest 4 dst +.temp 4 tmp +.floatparam 4 scalar +swapl tmp, src +x2 swapw dst, tmp + +.function _convert_item32_1_to_sc16_1_nswap_orc +.source 4 src +.dest 4 dst +.floatparam 4 scalar +.temp 4 tmp +x2 swapw tmp, src +swapl dst, tmp + +.function _convert_swap_byte_pairs_orc +.source 4 src +.dest 4 dst +swapl dst, src + +.function _convert_fc32_1_to_sc8_1_nswap_orc +.source 8 src +.dest 2 dst +.temp 8 tmp +.temp 4 tmp2 +.floatparam 4 scalar +x2 mulf tmp, src, scalar +x2 convfl tmp, tmp +swaplq tmp, tmp +x2 convlw tmp2, tmp +x2 convwb dst, tmp2 diff --git a/host/lib/convert/convert_with_neon.cpp b/host/lib/convert/convert_with_neon.cpp new file mode 100644 index 000000000..c7ad62104 --- /dev/null +++ b/host/lib/convert/convert_with_neon.cpp @@ -0,0 +1,61 @@ +// +// Copyright 2011-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "convert_common.hpp" +#include <arm_neon.h> + +using namespace uhd::convert; + +DECLARE_CONVERTER(fc32, 1, sc16_item32_le, 1, PRIORITY_SIMD){ + const fc32_t *input = reinterpret_cast<const fc32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + size_t i; + + float32x4_t Q0 = vdupq_n_f32(float(scale_factor)); + for (i=0; i < (nsamps & ~0x03); i+=2) { + float32x4_t Q1 = vld1q_f32(reinterpret_cast<const float *>(&input[i])); + float32x4_t Q2 = vmulq_f32(Q1, Q0); + int32x4_t Q3 = vcvtq_s32_f32(Q2); + int16x4_t D8 = vmovn_s32(Q3); + int16x4_t D9 = vrev32_s16(D8); + vst1_s16((reinterpret_cast<int16_t *>(&output[i])), D9); + } + + for (; i < nsamps; i++) + output[i] = fc32_to_item32_sc16(input[i], scale_factor); +} + +DECLARE_CONVERTER(sc16_item32_le, 1, fc32, 1, PRIORITY_SIMD){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + fc32_t *output = reinterpret_cast<fc32_t *>(outputs[0]); + + size_t i; + + float32x4_t Q1 = vdupq_n_f32(float(scale_factor)); + for (i=0; i < (nsamps & ~0x03); i+=2) { + int16x4_t D0 = vld1_s16(reinterpret_cast<const int16_t *>(&input[i])); + int16x4_t D1 = vrev32_s16(D0); + int32x4_t Q2 = vmovl_s16(D1); + float32x4_t Q3 = vcvtq_f32_s32(Q2); + float32x4_t Q4 = vmulq_f32(Q3, Q1); + vst1q_f32((reinterpret_cast<float *>(&output[i])), Q4); + } + + for (; i < nsamps; i++) + output[i] = item32_sc16_to_fc32(input[i], scale_factor); +} diff --git a/host/lib/convert/convert_with_orc.cpp b/host/lib/convert/convert_with_orc.cpp new file mode 100644 index 000000000..e44c8ca73 --- /dev/null +++ b/host/lib/convert/convert_with_orc.cpp @@ -0,0 +1,65 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> + +using namespace uhd::convert; + +extern "C" { +extern void _convert_fc32_1_to_item32_1_nswap_orc(void *, const void *, float, int); +extern void _convert_fc32_1_to_item32_1_bswap_orc(void *, const void *, float, int); +extern void _convert_item32_1_to_fc32_1_nswap_orc(void *, const void *, float, int); +extern void _convert_item32_1_to_fc32_1_bswap_orc(void *, const void *, float, int); +extern void _convert_sc16_1_to_item32_1_nswap_orc(void *, const void *, float, int); +extern void _convert_item32_1_to_sc16_1_nswap_orc(void *, const void *, float, int); +extern void _convert_fc32_1_to_sc8_1_nswap_orc(void *, const void *, float, int); +extern void _convert_swap_byte_pairs_orc(void *, const void *, int); +} + +DECLARE_CONVERTER(fc32, 1, sc16_item32_le, 1, PRIORITY_LIBORC){ + _convert_fc32_1_to_item32_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(fc32, 1, sc16_item32_be, 1, PRIORITY_LIBORC){ + _convert_fc32_1_to_item32_1_bswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(sc16_item32_le, 1, fc32, 1, PRIORITY_LIBORC){ + _convert_item32_1_to_fc32_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(sc16_item32_be, 1, fc32, 1, PRIORITY_LIBORC){ + _convert_item32_1_to_fc32_1_bswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(sc16, 1, sc16_item32_le, 1, PRIORITY_LIBORC){ + _convert_sc16_1_to_item32_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(sc16_item32_le, 1, sc16, 1, PRIORITY_LIBORC){ + _convert_item32_1_to_sc16_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(fc32, 1, sc8_item32_be, 1, PRIORITY_LIBORC){ + _convert_fc32_1_to_sc8_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); + _convert_swap_byte_pairs_orc(outputs[0], outputs[0], (nsamps + 1)/2); +} + +DECLARE_CONVERTER(fc32, 1, sc8_item32_le, 1, PRIORITY_LIBORC){ + _convert_fc32_1_to_sc8_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} diff --git a/host/lib/convert/convert_with_tables.cpp b/host/lib/convert/convert_with_tables.cpp new file mode 100644 index 000000000..cd7773d4b --- /dev/null +++ b/host/lib/convert/convert_with_tables.cpp @@ -0,0 +1,282 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> +#include <boost/math/special_functions/round.hpp> +#include <vector> + +using namespace uhd::convert; + +static const size_t sc16_table_len = size_t(1 << 16); + +typedef boost::uint16_t (*tohost16_type)(boost::uint16_t); + +/*********************************************************************** + * Implementation for sc16 to sc8 lookup table + * - Lookup the real and imaginary parts individually + **********************************************************************/ +template <bool swap> +class convert_sc16_1_to_sc8_item32_1 : public converter{ +public: + convert_sc16_1_to_sc8_item32_1(void): _table(sc16_table_len){} + + void set_scalar(const double scalar){ + for (size_t i = 0; i < sc16_table_len; i++){ + const boost::int16_t val = boost::uint16_t(i); + _table[i] = boost::int8_t(boost::math::iround(val * scalar / 32767.)); + } + } + + void operator()(const input_type &inputs, const output_type &outputs, const size_t nsamps){ + const sc16_t *input = reinterpret_cast<const sc16_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const size_t num_pairs = nsamps/2; + for (size_t i = 0, j = 0; i < num_pairs; i++, j+=2){ + output[i] = this->lookup(input[j], input[j+1]); + } + + if (nsamps != num_pairs*2){ + output[num_pairs] = this->lookup(input[nsamps-1], 0);; + } + } + + item32_t lookup(const sc16_t &in0, const sc16_t &in1){ + if (swap){ //hope this compiles out, its a template constant + return + (item32_t(_table[boost::uint16_t(in0.real())]) << 16) | + (item32_t(_table[boost::uint16_t(in0.imag())]) << 24) | + (item32_t(_table[boost::uint16_t(in1.real())]) << 0) | + (item32_t(_table[boost::uint16_t(in1.imag())]) << 8) ; + } + return + (item32_t(_table[boost::uint16_t(in0.real())]) << 8) | + (item32_t(_table[boost::uint16_t(in0.imag())]) << 0) | + (item32_t(_table[boost::uint16_t(in1.real())]) << 24) | + (item32_t(_table[boost::uint16_t(in1.imag())]) << 16) ; + } + +private: + std::vector<boost::uint8_t> _table; +}; + +/*********************************************************************** + * Implementation for sc16 lookup table + * - Lookup the real and imaginary parts individually + **********************************************************************/ +template <typename type, tohost16_type tohost, size_t re_shift, size_t im_shift> +class convert_sc16_item32_1_to_fcxx_1 : public converter{ +public: + convert_sc16_item32_1_to_fcxx_1(void): _table(sc16_table_len){} + + void set_scalar(const double scalar){ + for (size_t i = 0; i < sc16_table_len; i++){ + const boost::uint16_t val = tohost(boost::uint16_t(i & 0xffff)); + _table[i] = type(boost::int16_t(val)*scalar); + } + } + + void operator()(const input_type &inputs, const output_type &outputs, const size_t nsamps){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + std::complex<type> *output = reinterpret_cast<std::complex<type> *>(outputs[0]); + + for (size_t i = 0; i < nsamps; i++){ + const item32_t item = input[i]; + output[i] = std::complex<type>( + _table[boost::uint16_t(item >> re_shift)], + _table[boost::uint16_t(item >> im_shift)] + ); + } + } + +private: + std::vector<type> _table; +}; + +/*********************************************************************** + * Implementation for sc8 lookup table + * - Lookup the real and imaginary parts together + **********************************************************************/ +template <typename type, tohost16_type tohost, size_t lo_shift, size_t hi_shift> +class convert_sc8_item32_1_to_fcxx_1 : public converter{ +public: + convert_sc8_item32_1_to_fcxx_1(void): _table(sc16_table_len){} + + //special case for sc16 type, 32767 undoes float normalization + static type conv(const boost::int8_t &num, const double scalar){ + if (sizeof(type) == sizeof(s16_t)){ + return type(boost::math::iround(num*scalar*32767)); + } + return type(num*scalar); + } + + void set_scalar(const double scalar){ + for (size_t i = 0; i < sc16_table_len; i++){ + const boost::uint16_t val = tohost(boost::uint16_t(i & 0xffff)); + const type real = conv(boost::int8_t(val >> 8), scalar); + const type imag = conv(boost::int8_t(val >> 0), scalar); + _table[i] = std::complex<type>(real, imag); + } + } + + void operator()(const input_type &inputs, const output_type &outputs, const size_t nsamps){ + const item32_t *input = reinterpret_cast<const item32_t *>(size_t(inputs[0]) & ~0x3); + std::complex<type> *output = reinterpret_cast<std::complex<type> *>(outputs[0]); + + size_t num_samps = nsamps; + + if ((size_t(inputs[0]) & 0x3) != 0){ + const item32_t item0 = *input++; + *output++ = _table[boost::uint16_t(item0 >> hi_shift)]; + num_samps--; + } + + const size_t num_pairs = num_samps/2; + for (size_t i = 0, j = 0; i < num_pairs; i++, j+=2){ + const item32_t item_i = (input[i]); + output[j] = _table[boost::uint16_t(item_i >> lo_shift)]; + output[j + 1] = _table[boost::uint16_t(item_i >> hi_shift)]; + } + + if (num_samps != num_pairs*2){ + const item32_t item_n = input[num_pairs]; + output[num_samps-1] = _table[boost::uint16_t(item_n >> lo_shift)]; + } + } + +private: + std::vector<std::complex<type> > _table; +}; + +/*********************************************************************** + * Factory functions and registration + **********************************************************************/ + +#ifdef BOOST_BIG_ENDIAN +# define SHIFT_PAIR0 16, 0 +# define SHIFT_PAIR1 0, 16 +# define BE_SWAP false +# define LE_SWAP true +#else +# define SHIFT_PAIR0 0, 16 +# define SHIFT_PAIR1 16, 0 +# define BE_SWAP true +# define LE_SWAP false +#endif + +static converter::sptr make_convert_sc16_item32_be_1_to_fc32_1(void){ + return converter::sptr(new convert_sc16_item32_1_to_fcxx_1<float, uhd::ntohx, SHIFT_PAIR0>()); +} + +static converter::sptr make_convert_sc16_item32_be_1_to_fc64_1(void){ + return converter::sptr(new convert_sc16_item32_1_to_fcxx_1<double, uhd::ntohx, SHIFT_PAIR0>()); +} + +static converter::sptr make_convert_sc16_item32_le_1_to_fc32_1(void){ + return converter::sptr(new convert_sc16_item32_1_to_fcxx_1<float, uhd::wtohx, SHIFT_PAIR1>()); +} + +static converter::sptr make_convert_sc16_item32_le_1_to_fc64_1(void){ + return converter::sptr(new convert_sc16_item32_1_to_fcxx_1<double, uhd::wtohx, SHIFT_PAIR1>()); +} + +static converter::sptr make_convert_sc8_item32_be_1_to_fc32_1(void){ + return converter::sptr(new convert_sc8_item32_1_to_fcxx_1<float, uhd::ntohx, SHIFT_PAIR1>()); +} + +static converter::sptr make_convert_sc8_item32_be_1_to_fc64_1(void){ + return converter::sptr(new convert_sc8_item32_1_to_fcxx_1<double, uhd::ntohx, SHIFT_PAIR1>()); +} + +static converter::sptr make_convert_sc8_item32_le_1_to_fc32_1(void){ + return converter::sptr(new convert_sc8_item32_1_to_fcxx_1<float, uhd::wtohx, SHIFT_PAIR0>()); +} + +static converter::sptr make_convert_sc8_item32_le_1_to_fc64_1(void){ + return converter::sptr(new convert_sc8_item32_1_to_fcxx_1<double, uhd::wtohx, SHIFT_PAIR0>()); +} + +static converter::sptr make_convert_sc8_item32_be_1_to_sc16_1(void){ + return converter::sptr(new convert_sc8_item32_1_to_fcxx_1<s16_t, uhd::ntohx, SHIFT_PAIR1>()); +} + +static converter::sptr make_convert_sc8_item32_le_1_to_sc16_1(void){ + return converter::sptr(new convert_sc8_item32_1_to_fcxx_1<s16_t, uhd::wtohx, SHIFT_PAIR0>()); +} + +static converter::sptr make_convert_sc16_1_to_sc8_item32_be_1(void){ + return converter::sptr(new convert_sc16_1_to_sc8_item32_1<BE_SWAP>()); +} + +static converter::sptr make_convert_sc16_1_to_sc8_item32_le_1(void){ + return converter::sptr(new convert_sc16_1_to_sc8_item32_1<LE_SWAP>()); +} + +UHD_STATIC_BLOCK(register_convert_sc16_item32_1_to_fcxx_1){ + uhd::convert::id_type id; + id.num_inputs = 1; + id.num_outputs = 1; + + id.output_format = "fc32"; + id.input_format = "sc16_item32_be"; + uhd::convert::register_converter(id, &make_convert_sc16_item32_be_1_to_fc32_1, PRIORITY_TABLE); + + id.output_format = "fc64"; + id.input_format = "sc16_item32_be"; + uhd::convert::register_converter(id, &make_convert_sc16_item32_be_1_to_fc64_1, PRIORITY_TABLE); + + id.output_format = "fc32"; + id.input_format = "sc16_item32_le"; + uhd::convert::register_converter(id, &make_convert_sc16_item32_le_1_to_fc32_1, PRIORITY_TABLE); + + id.output_format = "fc64"; + id.input_format = "sc16_item32_le"; + uhd::convert::register_converter(id, &make_convert_sc16_item32_le_1_to_fc64_1, PRIORITY_TABLE); + + id.output_format = "fc32"; + id.input_format = "sc8_item32_be"; + uhd::convert::register_converter(id, &make_convert_sc8_item32_be_1_to_fc32_1, PRIORITY_TABLE); + + id.output_format = "fc64"; + id.input_format = "sc8_item32_be"; + uhd::convert::register_converter(id, &make_convert_sc8_item32_be_1_to_fc64_1, PRIORITY_TABLE); + + id.output_format = "fc32"; + id.input_format = "sc8_item32_le"; + uhd::convert::register_converter(id, &make_convert_sc8_item32_le_1_to_fc32_1, PRIORITY_TABLE); + + id.output_format = "fc64"; + id.input_format = "sc8_item32_le"; + uhd::convert::register_converter(id, &make_convert_sc8_item32_le_1_to_fc64_1, PRIORITY_TABLE); + + id.output_format = "sc16"; + id.input_format = "sc8_item32_be"; + uhd::convert::register_converter(id, &make_convert_sc8_item32_be_1_to_sc16_1, PRIORITY_TABLE); + + id.output_format = "sc16"; + id.input_format = "sc8_item32_le"; + uhd::convert::register_converter(id, &make_convert_sc8_item32_le_1_to_sc16_1, PRIORITY_TABLE); + + id.input_format = "sc16"; + id.output_format = "sc8_item32_be"; + uhd::convert::register_converter(id, &make_convert_sc16_1_to_sc8_item32_be_1, PRIORITY_TABLE); + + id.input_format = "sc16"; + id.output_format = "sc8_item32_le"; + uhd::convert::register_converter(id, &make_convert_sc16_1_to_sc8_item32_le_1, PRIORITY_TABLE); +} diff --git a/host/lib/convert/gen_convert_general.py b/host/lib/convert/gen_convert_general.py new file mode 100644 index 000000000..364c4bd1a --- /dev/null +++ b/host/lib/convert/gen_convert_general.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python +# +# Copyright 2011-2012 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +TMPL_HEADER = """ +#import time +/*********************************************************************** + * This file was generated by $file on $time.strftime("%c") + **********************************************************************/ + +\#include "convert_common.hpp" +\#include <uhd/utils/byteswap.hpp> + +using namespace uhd::convert; +""" + +TMPL_CONV_GEN2_ITEM32 = """ +DECLARE_CONVERTER(item32, 1, sc16_item32_$(end), 1, PRIORITY_GENERAL){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + for (size_t i = 0; i < nsamps; i++){ + output[i] = $(to_wire)(input[i]); + } +} + +DECLARE_CONVERTER(sc16_item32_$(end), 1, item32, 1, PRIORITY_GENERAL){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + for (size_t i = 0; i < nsamps; i++){ + output[i] = $(to_host)(input[i]); + } +} +""" + +TMPL_CONV_GEN2_SC16 = """ +DECLARE_CONVERTER($(cpu_type), 1, sc16_item32_$(end), 1, PRIORITY_GENERAL){ + const $(cpu_type)_t *input = reinterpret_cast<const $(cpu_type)_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + for (size_t i = 0; i < nsamps; i++){ + output[i] = $(to_wire)($(cpu_type)_to_item32_sc16(input[i], scale_factor)); + } +} + +DECLARE_CONVERTER(sc16_item32_$(end), 1, $(cpu_type), 1, PRIORITY_GENERAL){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + $(cpu_type)_t *output = reinterpret_cast<$(cpu_type)_t *>(outputs[0]); + + for (size_t i = 0; i < nsamps; i++){ + output[i] = item32_sc16_to_$(cpu_type)($(to_host)(input[i]), scale_factor); + } +} +""" + +TMPL_CONV_GEN2_SC8 = """ +DECLARE_CONVERTER(sc8_item32_$(end), 1, $(cpu_type), 1, PRIORITY_GENERAL){ + const item32_t *input = reinterpret_cast<const item32_t *>(size_t(inputs[0]) & ~0x3); + $(cpu_type)_t *output = reinterpret_cast<$(cpu_type)_t *>(outputs[0]); + $(cpu_type)_t dummy; + size_t num_samps = nsamps; + + if ((size_t(inputs[0]) & 0x3) != 0){ + const item32_t item0 = $(to_host)(*input++); + item32_sc8_to_$(cpu_type)(item0, dummy, *output++, scale_factor); + num_samps--; + } + + const size_t num_pairs = num_samps/2; + for (size_t i = 0, j = 0; i < num_pairs; i++, j+=2){ + const item32_t item_i = $(to_host)(input[i]); + item32_sc8_to_$(cpu_type)(item_i, output[j], output[j+1], scale_factor); + } + + if (num_samps != num_pairs*2){ + const item32_t item_n = $(to_host)(input[num_pairs]); + item32_sc8_to_$(cpu_type)(item_n, output[num_samps-1], dummy, scale_factor); + } +} + +DECLARE_CONVERTER($(cpu_type), 1, sc8_item32_$(end), 1, PRIORITY_GENERAL){ + const $(cpu_type)_t *input = reinterpret_cast<const $(cpu_type)_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + const size_t num_pairs = nsamps/2; + for (size_t i = 0, j = 0; i < num_pairs; i++, j+=2){ + const item32_t item = $(cpu_type)_to_item32_sc8(input[j], input[j+1], scale_factor); + output[i] = $(to_wire)(item); + } + + if (nsamps != num_pairs*2){ + const item32_t item = $(cpu_type)_to_item32_sc8(input[nsamps-1], 0, scale_factor); + output[num_pairs] = $(to_wire)(item); + } +} +""" + +TMPL_CONV_USRP1_COMPLEX = """ +DECLARE_CONVERTER($(cpu_type), $(width), sc16_item16_usrp1, 1, PRIORITY_GENERAL){ + #for $w in range($width) + const $(cpu_type)_t *input$(w) = reinterpret_cast<const $(cpu_type)_t *>(inputs[$(w)]); + #end for + boost::uint16_t *output = reinterpret_cast<boost::uint16_t *>(outputs[0]); + + for (size_t i = 0, j = 0; i < nsamps; i++){ + #for $w in range($width) + output[j++] = $(to_wire)(boost::uint16_t(boost::int16_t(input$(w)[i].real()$(do_scale)))); + output[j++] = $(to_wire)(boost::uint16_t(boost::int16_t(input$(w)[i].imag()$(do_scale)))); + #end for + } +} + +DECLARE_CONVERTER(sc16_item16_usrp1, 1, $(cpu_type), $(width), PRIORITY_GENERAL){ + const boost::uint16_t *input = reinterpret_cast<const boost::uint16_t *>(inputs[0]); + #for $w in range($width) + $(cpu_type)_t *output$(w) = reinterpret_cast<$(cpu_type)_t *>(outputs[$(w)]); + #end for + + for (size_t i = 0, j = 0; i < nsamps; i++){ + #for $w in range($width) + output$(w)[i] = $(cpu_type)_t( + boost::int16_t($(to_host)(input[j+0]))$(do_scale), + boost::int16_t($(to_host)(input[j+1]))$(do_scale) + ); + j += 2; + #end for + } +} + +DECLARE_CONVERTER(sc8_item16_usrp1, 1, $(cpu_type), $(width), PRIORITY_GENERAL){ + const boost::uint16_t *input = reinterpret_cast<const boost::uint16_t *>(inputs[0]); + #for $w in range($width) + $(cpu_type)_t *output$(w) = reinterpret_cast<$(cpu_type)_t *>(outputs[$(w)]); + #end for + + for (size_t i = 0, j = 0; i < nsamps; i++){ + #for $w in range($width) + { + const boost::uint16_t num = $(to_host)(input[j++]); + output$(w)[i] = $(cpu_type)_t( + boost::int8_t(num)$(do_scale), + boost::int8_t(num >> 8)$(do_scale) + ); + } + #end for + } +} +""" + +def parse_tmpl(_tmpl_text, **kwargs): + from Cheetah.Template import Template + return str(Template(_tmpl_text, kwargs)) + +if __name__ == '__main__': + import sys, os + file = os.path.basename(__file__) + output = parse_tmpl(TMPL_HEADER, file=file) + + #generate complex converters for all gen2 platforms + for end, to_host, to_wire in ( + ('be', 'uhd::ntohx', 'uhd::htonx'), + ('le', 'uhd::wtohx', 'uhd::htowx'), + ): + for cpu_type in 'fc64', 'fc32', 'sc16': + output += parse_tmpl( + TMPL_CONV_GEN2_SC16, + end=end, to_host=to_host, to_wire=to_wire, cpu_type=cpu_type + ) + for cpu_type in 'fc64', 'fc32', 'sc16', 'sc8': + output += parse_tmpl( + TMPL_CONV_GEN2_SC8, + end=end, to_host=to_host, to_wire=to_wire, cpu_type=cpu_type + ) + output += parse_tmpl( + TMPL_CONV_GEN2_ITEM32, + end=end, to_host=to_host, to_wire=to_wire + ) + + #generate complex converters for usrp1 format + for width in 1, 2, 4: + for cpu_type, do_scale in ( + ('fc64', '*scale_factor'), + ('fc32', '*float(scale_factor)'), + ('sc16', ''), + ): + output += parse_tmpl( + TMPL_CONV_USRP1_COMPLEX, + width=width, to_host='uhd::wtohx', to_wire='uhd::htowx', + cpu_type=cpu_type, do_scale=do_scale + ) + open(sys.argv[1], 'w').write(output) diff --git a/host/lib/deprecated.cpp b/host/lib/deprecated.cpp new file mode 100644 index 000000000..0fc751eeb --- /dev/null +++ b/host/lib/deprecated.cpp @@ -0,0 +1,81 @@ +//---------------------------------------------------------------------- +//-- deprecated interfaces below, to be removed when the API is changed +//---------------------------------------------------------------------- + +#include <uhd/types/otw_type.hpp> +#include <uhd/types/io_type.hpp> +#include <boost/cstdint.hpp> +#include <stdexcept> +#include <complex> +#include <vector> + +using namespace uhd; + +/*********************************************************************** + * otw type + **********************************************************************/ +size_t otw_type_t::get_sample_size(void) const{ + return (this->width * 2) / 8; +} + +otw_type_t::otw_type_t(void): + width(0), + shift(0), + byteorder(BO_NATIVE) +{ + /* NOP */ +} + +/*********************************************************************** + * io type + **********************************************************************/ +static std::vector<size_t> get_tid_size_table(void){ + std::vector<size_t> table(128, 0); + table[size_t(io_type_t::COMPLEX_FLOAT64)] = sizeof(std::complex<double>); + table[size_t(io_type_t::COMPLEX_FLOAT32)] = sizeof(std::complex<float>); + table[size_t(io_type_t::COMPLEX_INT16)] = sizeof(std::complex<boost::int16_t>); + table[size_t(io_type_t::COMPLEX_INT8)] = sizeof(std::complex<boost::int8_t>); + return table; +} + +static const std::vector<size_t> tid_size_table(get_tid_size_table()); + +io_type_t::io_type_t(tid_t tid): + size(tid_size_table[size_t(tid) & 0x7f]), tid(tid) +{ + /* NOP */ +} + +io_type_t::io_type_t(size_t size): + size(size), tid(CUSTOM_TYPE) +{ + /* NOP */ +} + +#include <uhd/types/clock_config.hpp> + +using namespace uhd; + +clock_config_t clock_config_t::external(void){ + clock_config_t clock_config; + clock_config.ref_source = clock_config_t::REF_SMA; + clock_config.pps_source = clock_config_t::PPS_SMA; + clock_config.pps_polarity = clock_config_t::PPS_POS; + return clock_config; +} + +clock_config_t clock_config_t::internal(void){ + clock_config_t clock_config; + clock_config.ref_source = clock_config_t::REF_INT; + clock_config.pps_source = clock_config_t::PPS_SMA; + clock_config.pps_polarity = clock_config_t::PPS_POS; + return clock_config; +} + +clock_config_t::clock_config_t(void): + ref_source(REF_INT), + pps_source(PPS_SMA), + pps_polarity(PPS_POS) +{ + /* NOP */ +} diff --git a/host/lib/device.cpp b/host/lib/device.cpp new file mode 100644 index 000000000..c5bc047da --- /dev/null +++ b/host/lib/device.cpp @@ -0,0 +1,155 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/device.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/functional/hash.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/thread/mutex.hpp> + +using namespace uhd; + +static boost::mutex _device_mutex; + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +/*! + * Make a device hash that maps 1 to 1 with a device address. + * The hash will be used to identify created devices. + * \param dev_addr the device address + * \return the hash number + */ +static size_t hash_device_addr( + const device_addr_t &dev_addr +){ + //combine the hashes of sorted keys/value pairs + size_t hash = 0; + BOOST_FOREACH(const std::string &key, uhd::sorted(dev_addr.keys())){ + boost::hash_combine(hash, key); + boost::hash_combine(hash, dev_addr[key]); + } + return hash; +} + +/*********************************************************************** + * Registration + **********************************************************************/ +typedef boost::tuple<device::find_t, device::make_t> dev_fcn_reg_t; + +// instantiate the device function registry container +UHD_SINGLETON_FCN(std::vector<dev_fcn_reg_t>, get_dev_fcn_regs) + +void device::register_device( + const find_t &find, + const make_t &make +){ + UHD_LOGV(always) << "registering device" << std::endl; + get_dev_fcn_regs().push_back(dev_fcn_reg_t(find, make)); +} + +/*********************************************************************** + * Discover + **********************************************************************/ +device_addrs_t device::find(const device_addr_t &hint){ + boost::mutex::scoped_lock lock(_device_mutex); + + device_addrs_t device_addrs; + + BOOST_FOREACH(const dev_fcn_reg_t &fcn, get_dev_fcn_regs()){ + try{ + device_addrs_t discovered_addrs = fcn.get<0>()(hint); + device_addrs.insert( + device_addrs.begin(), + discovered_addrs.begin(), + discovered_addrs.end() + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Device discovery error: " << e.what() << std::endl; + } + } + + return device_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +device::sptr device::make(const device_addr_t &hint, size_t which){ + boost::mutex::scoped_lock lock(_device_mutex); + + typedef boost::tuple<device_addr_t, make_t> dev_addr_make_t; + std::vector<dev_addr_make_t> dev_addr_makers; + + BOOST_FOREACH(const dev_fcn_reg_t &fcn, get_dev_fcn_regs()){ + BOOST_FOREACH(device_addr_t dev_addr, fcn.get<0>()(hint)){ + //append the discovered address and its factory function + dev_addr_makers.push_back(dev_addr_make_t(dev_addr, fcn.get<1>())); + } + } + + //check that we found any devices + if (dev_addr_makers.size() == 0){ + throw uhd::key_error(str( + boost::format("No devices found for ----->\n%s") % hint.to_pp_string() + )); + } + + //check that the which index is valid + if (dev_addr_makers.size() <= which){ + throw uhd::index_error(str( + boost::format("No device at index %d for ----->\n%s") % which % hint.to_pp_string() + )); + } + + //create a unique hash for the device address + device_addr_t dev_addr; make_t maker; + boost::tie(dev_addr, maker) = dev_addr_makers.at(which); + size_t dev_hash = hash_device_addr(dev_addr); + UHD_LOG << boost::format("Device hash: %u") % dev_hash << std::endl; + + //copy keys that were in hint but not in dev_addr + //this way, we can pass additional transport arguments + BOOST_FOREACH(const std::string &key, hint.keys()){ + if (not dev_addr.has_key(key)) dev_addr[key] = hint[key]; + } + + //map device address hash to created devices + static uhd::dict<size_t, boost::weak_ptr<device> > hash_to_device; + + //try to find an existing device + try{ + UHD_ASSERT_THROW(hash_to_device.has_key(dev_hash)); + UHD_ASSERT_THROW(not hash_to_device[dev_hash].expired()); + return hash_to_device[dev_hash].lock(); + } + //create and register a new device + catch(const uhd::assertion_error &){ + device::sptr dev = maker(dev_addr); + hash_to_device[dev_hash] = dev; + return dev; + } +} diff --git a/host/lib/exception.cpp b/host/lib/exception.cpp new file mode 100644 index 000000000..ea056bd3b --- /dev/null +++ b/host/lib/exception.cpp @@ -0,0 +1,45 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/exception.hpp> +#include <boost/functional/hash.hpp> +#include <boost/format.hpp> + +using namespace uhd; + +exception::exception(const std::string &what): + std::runtime_error(what){/* NOP */} + +#define make_exception_impl(name, class, base) \ + class::class(const std::string &what): \ + base(str(boost::format("%s: %s") % name % what)){} \ + unsigned class::code(void) const{return boost::hash<std::string>()(#class) & 0xfff;} \ + class *class::dynamic_clone(void) const{return new class(*this);} \ + void class::dynamic_throw(void) const{throw *this;} + +make_exception_impl("AssertionError", assertion_error, exception) +make_exception_impl("LookupError", lookup_error, exception) +make_exception_impl("IndexError", index_error, lookup_error) +make_exception_impl("KeyError", key_error, lookup_error) +make_exception_impl("TypeError", type_error, exception) +make_exception_impl("ValueError", value_error, exception) +make_exception_impl("RuntimeError", runtime_error, exception) +make_exception_impl("NotImplementedError", not_implemented_error, runtime_error) +make_exception_impl("EnvironmentError", environment_error, exception) +make_exception_impl("IOError", io_error, environment_error) +make_exception_impl("OSError", os_error, environment_error) +make_exception_impl("SystemError", system_error, exception) diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt new file mode 100644 index 000000000..dc2ab7847 --- /dev/null +++ b/host/lib/ic_reg_maps/CMakeLists.txt @@ -0,0 +1,105 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +SET(LIBUHD_PYTHON_GEN_SOURCE_DEPS ${CMAKE_CURRENT_SOURCE_DIR}/common.py) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf4350_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/adf4350_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf4351_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/adf4351_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf4360_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/adf4360_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad9510_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/ad9510_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad9777_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/ad9777_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad5623_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/ad5623_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad7922_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/ad7922_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2829_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/max2829_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2118_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/max2118_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2112_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/max2112_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2112_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/max2112_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad9862_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/ad9862_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad9522_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/ad9522_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_ads62p44_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/ads62p44_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_tuner_4937di5_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/tuner_4937di5_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_tda18272hnm_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/tda18272hnm_regs.hpp +) + +UNSET(LIBUHD_PYTHON_GEN_SOURCE_DEPS) diff --git a/host/lib/ic_reg_maps/common.py b/host/lib/ic_reg_maps/common.py new file mode 100644 index 000000000..24f5bf8be --- /dev/null +++ b/host/lib/ic_reg_maps/common.py @@ -0,0 +1,196 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import re +import sys +import math +from Cheetah.Template import Template + +COMMON_TMPL = """\ +#import time +/*********************************************************************** + * This file was generated by $file on $time.strftime("%c") + **********************************************************************/ + +\#ifndef INCLUDED_$(name.upper())_HPP +\#define INCLUDED_$(name.upper())_HPP + +\#include <uhd/config.hpp> +\#include <uhd/exception.hpp> +\#include <boost/cstdint.hpp> +\#include <set> + +class $(name)_t{ +public: + #for $reg in $regs + #if $reg.get_enums() + enum $reg.get_type(){ + #for $i, $enum in enumerate($reg.get_enums()) + #set $end_comma = ',' if $i < len($reg.get_enums())-1 else '' + $(reg.get_name().upper())_$(enum[0].upper()) = $enum[1]$end_comma + #end for + }; + #end if + $reg.get_type() $reg.get_name(); + #end for + + $(name)_t(void){ + _state = NULL; + #for $reg in $regs + $reg.get_name() = $reg.get_default(); + #end for + } + + ~$(name)_t(void){ + delete _state; + } + + $body + + void save_state(void){ + if (_state == NULL) _state = new $(name)_t(); + #for $reg in $regs + _state->$reg.get_name() = this->$reg.get_name(); + #end for + } + + template<typename T> std::set<T> get_changed_addrs(void){ + if (_state == NULL) throw uhd::runtime_error("no saved state"); + //check each register for changes + std::set<T> addrs; + #for $reg in $regs + if(_state->$reg.get_name() != this->$reg.get_name()){ + addrs.insert($reg.get_addr()); + } + #end for + return addrs; + } + + #for $mreg in $mregs + $mreg.get_type() get_$(mreg.get_name())(void){ + return + #set $shift = 0 + #for $reg in $mreg.get_regs() + ($(mreg.get_type())($reg.get_name() & $reg.get_mask()) << $shift) | + #set $shift = $shift + $reg.get_bit_width() + #end for + 0; + } + + void set_$(mreg.get_name())($mreg.get_type() reg){ + #set $shift = 0 + #for $reg in $mreg.get_regs() + $reg.get_name() = (reg >> $shift) & $reg.get_mask(); + #set $shift = $shift + $reg.get_bit_width() + #end for + } + + #end for +private: + $(name)_t *_state; +}; + +\#endif /* INCLUDED_$(name.upper())_HPP */ +""" + +def parse_tmpl(_tmpl_text, **kwargs): + return str(Template(_tmpl_text, kwargs)) + +def to_num(arg): return int(eval(arg)) + +class reg: + def __init__(self, reg_des): + try: self.parse(reg_des) + except Exception, e: + raise Exception, 'Error parsing register description: "%s"\nWhat: %s'%(reg_des, e) + + def parse(self, reg_des): + x = re.match('^(\w*)\s*(\w*)\[(.*)\]\s*(\w*)\s*(.*)$', reg_des) + name, addr, bit_range, default, enums = x.groups() + + #store variables + self._name = name + self._addr = to_num(addr) + if ':' in bit_range: self._addr_spec = sorted(map(int, bit_range.split(':'))) + else: self._addr_spec = int(bit_range), int(bit_range) + self._default = to_num(default) + + #extract enum + self._enums = list() + if enums: + enum_val = 0 + for enum_str in map(str.strip, enums.split(',')): + if '=' in enum_str: + enum_name, enum_val = enum_str.split('=') + enum_val = to_num(enum_val) + else: enum_name = enum_str + self._enums.append((enum_name, enum_val)) + enum_val += 1 + + def get_addr(self): return self._addr + def get_enums(self): return self._enums + def get_name(self): return self._name + def get_default(self): + for key, val in self.get_enums(): + if val == self._default: return str.upper('%s_%s'%(self.get_name(), key)) + return self._default + def get_type(self): + if self.get_enums(): return '%s_t'%self.get_name() + return 'boost::uint%d_t'%max(2**math.ceil(math.log(self.get_bit_width(), 2)), 8) + def get_shift(self): return self._addr_spec[0] + def get_mask(self): return hex(int('1'*self.get_bit_width(), 2)) + def get_bit_width(self): return self._addr_spec[1] - self._addr_spec[0] + 1 + +class mreg: + def __init__(self, mreg_des, regs): + try: self.parse(mreg_des, regs) + except Exception, e: + raise Exception, 'Error parsing meta register description: "%s"\nWhat: %s'%(mreg_des, e) + + def parse(self, mreg_des, regs): + x = re.match('^~(\w*)\s+(.*)\s*$', mreg_des) + self._name, reg_names = x.groups() + regs_dict = dict([(reg.get_name(), reg) for reg in regs]) + self._regs = [regs_dict[reg_name] for reg_name in map(str.strip, reg_names.split(','))] + + def get_name(self): return self._name + def get_regs(self): return self._regs + def get_bit_width(self): return sum(map(reg.get_bit_width, self._regs)) + def get_type(self): + return 'boost::uint%d_t'%max(2**math.ceil(math.log(self.get_bit_width(), 2)), 8) + +def generate(name, regs_tmpl, body_tmpl='', file=__file__, append=False): + #evaluate the regs template and parse each line into a register + regs = list(); mregs = list() + for entry in parse_tmpl(regs_tmpl).splitlines(): + if entry.startswith('~'): mregs.append(mreg(entry, regs)) + else: regs.append(reg(entry)) + + #evaluate the body template with the list of registers + body = '\n '.join(parse_tmpl(body_tmpl, regs=regs).splitlines()) + + #evaluate the code template with the parsed registers and arguments + code = parse_tmpl(COMMON_TMPL, + name=name, + regs=regs, + mregs=mregs, + body=body, + file=file, + ) + + #write the generated code to file specified by argv1 + open(sys.argv[1], 'a' if append else 'w').write(code) diff --git a/host/lib/ic_reg_maps/gen_ad5623_regs.py b/host/lib/ic_reg_maps/gen_ad5623_regs.py new file mode 100755 index 000000000..e653921ba --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad5623_regs.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +data 0[4:15] 0 +addr 0[16:18] 0 DAC_A=0, DAC_B=1, ALL=7 +cmd 0[19:21] 0 wr_input_n, up_dac_n, wr_input_n_up_all, wr_up_dac_chan_n, power_down, reset, load_ldac +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint32_t get_reg(void){ + boost::uint32_t reg = 0; + #for $reg in filter(lambda r: r.get_addr() == 0, $regs) + reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + return reg; +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='ad5623_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_ad7922_regs.py b/host/lib/ic_reg_maps/gen_ad7922_regs.py new file mode 100755 index 000000000..5cec1924a --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad7922_regs.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +result 0[0:11] 0 +mod 0[12] 0 +chn 0[13] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint16_t get_reg(void){ + boost::uint16_t reg = 0; + #for $reg in filter(lambda r: r.get_addr() == 0, $regs) + reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + return reg; +} + +void set_reg(boost::uint16_t reg){ + #for $reg in filter(lambda r: r.get_addr() == 0, $regs) + $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); + #end for +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='ad7922_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_ad9510_regs.py b/host/lib/ic_reg_maps/gen_ad9510_regs.py new file mode 100755 index 000000000..83236c921 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad9510_regs.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## serial control port config +######################################################################## +long_instruction 0[4] 1 8bits, 16bits +soft_reset 0[5] 0 +lsb_first 0[6] 0 msb, lsb +sdo_inactive 0[7] 0 active, inactive +######################################################################## +## pll settings +######################################################################## +acounter 4[0:5] 0 +bcounter_msb 5[0:4] 0 +bcounter_lsb 6[0:7] 0 +lor_enable 7[2] 0 enb, dis +lor_ildd 7[5:6] 0 3cyc, 6cyc, 12cyc, 24cyc +charge_pump_mode 8[0:1] 0 3state, pump_up, pump_down, normal +pll_mux_control 8[2:5] 0 off, dld_high, ndiv, dld_low, rdiv, ald_nchan, acounter, prescaler, pfd_up, pfd_down, lor_high, 3state, ald_pchan, lor_lol_high, lor_lol_low, lor_low +pfd_polarity 8[6] 0 neg, pos +reset_all_counters 9[0] 0 +ncounter_reset 9[1] 0 +rcounter_reset 9[2] 0 +cp_current_setting 9[4:6] 0 0_60ma, 1_2ma, 1_8ma, 2_4ma, 3_0ma, 3_6ma, 4_2ma, 4_8ma +pll_power_down 0xA[0:1] 0 normal=0, async_pd=1, sync_pd=3 +prescaler_value 0xA[2:4] 0 div1, div2, 2_3, 4_5, 8_9, 16_17, 32_33, div3 +b_counter_bypass 0xA[6] 0 +ref_counter_msb 0xB[0:5] 0 +ref_counter_lsb 0xC[0:7] 0 +antibacklash_pw 0xD[0:1] 0 1_3ns, 2_9ns, 6_0ns +dld_window 0xD[5] 0 9_5ns, 3_5ns +lock_detect_disable 0xD[6] 0 enb, dis +######################################################################## +## fine delay adjust +######################################################################## +#for $i, $o in ((5, 0), (6, 4)) +delay_control_out$i $hex(0x34+$o)[0] 0 +ramp_current_out$i $hex(0x35+$o)[0:2] 0 200ua, 400ua, 600ua, 800ua, 1000ua, 1200ua, 1400ua, 1600ua +ramp_capacitor_out$i $hex(0x35+$o)[3:5] 0 4caps=0, 3caps=1, 2caps=3, 1cap=7 +delay_fine_adjust_out$i $hex(0x36+$o)[1:5] 0 +#end for +######################################################################## +## outputs +######################################################################## +#for $i, $o in ((0, 0), (1, 1), (2, 2), (3, 3)) +power_down_lvpecl_out$i $hex(0x3C+$o)[0:1] 0 normal, test, safe_pd, total_pd +output_level_lvpecl_out$i $hex(0x3C+$o)[2:3] 2 500mv, 340mv, 810mv, 660mv +#end for +#for $i, $o in ((4, 0), (5, 1), (6, 2), (7, 3)) +power_down_lvds_cmos_out$i $hex(0x40+$o)[0] 0 +output_level_lvds_out$i $hex(0x40+$o)[1:2] 1 1_75ma, 3_5ma, 5_25ma, 7ma +lvds_cmos_select_out$i $hex(0x40+$o)[3] 1 lvds, cmos +inverted_cmos_driver_out$i $hex(0x40+$o)[4] 0 dis, enb +#end for +clock_select 45[0] 1 clk2_drives, clk1_drives +clk1_power_down 45[1] 0 +clk2_power_down 45[2] 0 +prescaler_clock_pd 45[3] 0 +refin_power_down 45[4] 0 +all_clock_inputs_pd 45[5] 0 +######################################################################## +## dividers +######################################################################## +#for $i, $o in ((0, 0), (1, 2), (2, 4), (3, 6), (4, 8), (5, 10), (6, 12), (7, 14)) +divider_high_cycles_out$i $hex(0x48+$o)[0:3] 0 +divider_low_cycles_out$i $hex(0x48+$o)[4:7] 0 +phase_offset_out$i $hex(0x49+$o)[0:3] 0 +start_out$i $hex(0x49+$o)[4] 0 +force_out$i $hex(0x49+$o)[5] 0 +nosync_out$i $hex(0x49+$o)[6] 0 +bypass_divider_out$i $hex(0x49+$o)[7] 0 +#end for +######################################################################## +## function +######################################################################## +sync_detect_enable 58[0] 0 dis, enb +sync_select 58[1] 0 1_to_0_5, 0_5_to_1 +soft_sync 58[2] 0 +dist_power_down 58[3] 0 +sync_power_down 58[4] 0 +function_pin_select 58[5:6] 0 resetb, syncb, test, pdb +update_registers 0x5A[0] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint16_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return reg; +} + +boost::uint32_t get_write_reg(boost::uint16_t addr){ + return (boost::uint32_t(addr) << 8) | get_reg(addr); +} + +boost::uint32_t get_read_reg(boost::uint16_t addr){ + return (boost::uint32_t(addr) << 8) | (1 << 23); +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='ad9510_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_ad9522_regs.py b/host/lib/ic_reg_maps/gen_ad9522_regs.py new file mode 100755 index 000000000..1512da811 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad9522_regs.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +sdo_active 0x000[7] 0 sdio, sdo_sdio +lsb_first_addr_incr 0x000[6] 0 msb, lsb +soft_reset 0x000[5] 0 +mirror 0x000[3:0] 0 +readback_active_registers 0x004[0] 0 buffer, active +pfd_polarity 0x010[7] 0 pos, neg +cp_current 0x010[6:4] 7 0_6ma, 1_2ma, 1_8ma, 2_4ma, 3_0ma, 3_6ma, 4_2ma, 4_8ma +cp_mode 0x010[3:2] 3 high_imp, force_source, force_sink, normal +pll_power_down 0x010[1:0] 1 normal=0, async=1, sync=3 +r_counter_lsb 0x011[7:0] 1 +r_counter_msb 0x012[5:0] 0 +~r_counter r_counter_lsb, r_counter_msb +a_counter 0x013[5:0] 0 +b_counter_lsb 0x014[7:0] 3 +b_counter_msb 0x015[4:0] 0 +~b_counter b_counter_lsb, b_counter_msb +set_cp_pin_to_vcp_2 0x016[7] 0 normal, vcp_2 +reset_r_counter 0x016[6] 0 +reset_a_and_b_counters 0x016[5] 0 +reset_all_counters 0x016[4] 0 +b_counter_bypass 0x016[3] 0 normal, div1 +prescaler_p 0x016[2:0] 6 div1, div2, div2_3, div4_5, div8_9, div16_17, div32_33, div3 +status_pin_control 0x017[7:2] 0 +antibacklash_pulse_width 0x017[1:0] 0 2_9ns, 1_3ns, 6_0ns +enb_cmos_ref_input_dc_off 0x018[7] 0 +lock_detect_counter 0x018[6:5] 0 5cyc, 16cyc, 64cyc, 255cyc +digital_lock_detect_window 0x018[4] 0 high_range, low_range +disable_digital_lock_detect 0x018[3] 0 normal, disabled +vco_calibration_divider 0x018[2:1] 3 div2, div4, div8, div16 +vco_calibration_now 0x018[0] 0 +r_a_b_counters_sync_pin_rst 0x019[7:6] 0 nothing, async, sync +r_path_delay 0x019[5:3] 0 +n_path_delay 0x019[2:0] 0 +enable_status_pin_divider 0x01A[7] 0 +ref_freq_monitor_threshold 0x01A[6] 0 1_02mhz, 6khz +ld_pin_control 0x01A[5:0] 0 +enable_vco_freq_monitor 0x01B[7] 0 +enable_ref2_freq_monitor 0x01B[6] 0 +enable_ref1_freq_monitor 0x01B[5] 0 +refmon_pin_control 0x01B[4:0] 0 +disable_switchover_deglitch 0x01C[7] 0 +select_ref 0x01C[6] 0 ref1, ref2 +use_ref_sel_pin 0x01C[5] 0 register, ref_sel +enb_auto_ref_switchover 0x01C[4] 0 manual, auto +stay_on_ref2 0x01C[3] 0 return_ref1, stay_ref2 +enable_ref2 0x01C[2] 0 +enable_ref1 0x01C[1] 0 +enable_differential_ref 0x01C[0] 0 +enb_stat_eeprom_at_stat_pin 0x01D[7] 1 +enable_xtal_osc 0x01D[6] 0 +enable_clock_doubler 0x01D[5] 0 +disable_pll_status_reg 0x01D[4] 0 +enable_ld_pin_comparator 0x01D[3] 0 +enable_external_holdover 0x01D[1] 0 +enable_holdover 0x01D[0] 0 +external_zero_delay_fcds 0x01E[4:3] 0 +enable_external_zero_delay 0x01E[2] 0 +enable_zero_delay 0x01E[1] 0 +######################################################################## +vco_calibration_finished 0x01F[6] 0 +holdover_active 0x01F[5] 0 +ref2_selected 0x01F[4] 0 +vco_freq_gt_thresh 0x01F[3] 0 +ref2_freq_gt_thresh 0x01F[2] 0 +ref1_freq_gt_thresh 0x01F[1] 0 +digital_lock_detect 0x01F[0] 0 +######################################################################## +#for $i in range(12) +#set $addr = ($i + 0x0F0) +out$(i)_format $(addr)[7] 0 lvds, cmos +out$(i)_cmos_configuration $(addr)[6:5] 3 off, a_on, b_on, ab_on +out$(i)_polarity $(addr)[4:3] 0 lvds_a_non_b_inv=0, lvds_a_inv_b_non=1, cmos_ab_non=0, cmos_ab_inv=1, cmos_a_non_b_inv=2, cmos_a_inv_b_non=3 +out$(i)_lvds_diff_voltage $(addr)[2:1] 1 1_75ma, 3_5ma, 5_25ma, 7_0ma +out$(i)_lvds_power_down $(addr)[0] 0 +#end for +######################################################################## +#for $i in reversed(range(8)) +csdld_en_out_$i 0x0FC[$i] 0 ignore, async +#end for +######################################################################## +#for $i in reversed(range(4)) +csdld_en_out_$(8 + $i) 0x0FD[$i] 0 ignore, async +#end for +######################################################################## +#set $default_val = 0x7 +#for $i in range(4) +#set $addr0 = hex($i*3 + 0x190) +#set $addr1 = hex($i*3 + 0x191) +#set $addr2 = hex($i*3 + 0x192) +divider$(i)_low_cycles $(addr0)[7:4] $default_val +divider$(i)_high_cycles $(addr0)[3:0] $default_val +divider$(i)_bypass $(addr1)[7] 0 +divider$(i)_ignore_sync $(addr1)[6] 0 +divider$(i)_force_high $(addr1)[5] 0 +divider$(i)_start_high $(addr1)[4] 0 +divider$(i)_phase_offset $(addr1)[3:0] 0 +channel$(i)_power_down $(addr2)[2] 0 +disable_divider$(i)_ddc $(addr2)[0] 0 +#set $default_val /= 2 +#end for +######################################################################## +vco_divider 0x1E0[2:0] 2 div2, div3, div4, div5, div6, static, div1 +power_down_clock_input_sel 0x1E1[4] 0 +power_down_vco_clock_ifc 0x1E1[3] 0 +power_down_vco_and_clock 0x1E1[2] 0 +select_vco_or_clock 0x1E1[1] 0 external, vco +bypass_vco_divider 0x1E1[0] 0 +disable_power_on_sync 0x230[3] 0 +power_down_sync 0x230[2] 0 +power_down_dist_ref 0x230[1] 0 +soft_sync 0x230[0] 0 +io_update 0x232[0] 0 +soft_eeprom 0xB02[1] 0 +enable_eeprom_write 0xB02[0] 0 +reg2eeprom 0xB03[0] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint32_t get_reg(boost::uint16_t addr){ + boost::uint32_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + if (addr == 0){ //mirror 4 bits in register 0 + reg |= ((reg >> 7) & 0x1) << 0; + reg |= ((reg >> 6) & 0x1) << 1; + reg |= ((reg >> 5) & 0x1) << 2; + reg |= ((reg >> 4) & 0x1) << 3; + } + return reg; +} + +void set_reg(boost::uint16_t addr, boost::uint32_t reg){ + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); + #end for + break; + #end for + } +} + +boost::uint32_t get_write_reg(boost::uint16_t addr){ + return (boost::uint32_t(addr) << 8) | get_reg(addr); +} + +boost::uint32_t get_read_reg(boost::uint16_t addr){ + return (boost::uint32_t(addr) << 8) | (1 << 23); +} + +""" + +if __name__ == '__main__': + import common; common.generate( + name='ad9522_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_ad9777_regs.py b/host/lib/ic_reg_maps/gen_ad9777_regs.py new file mode 100755 index 000000000..47b61cf44 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad9777_regs.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +sdio_bidirectional 0[7] 0 input, io +lsb_msb_first 0[6] 0 msb, lsb +soft_reset 0[5] 0 +sleep_mode 0[4] 0 +power_down_mode 0[3] 0 +x_1r_2r_mode 0[2] 0 2r, 1r +pll_lock_indicator 0[1] 0 +######################################################################## +## address 1 +######################################################################## +filter_interp_rate 1[6:7] 0 1x, 2x, 4x, 8x +modulation_mode 1[4:5] 0 none, fs_2, fs_4, fs_8 +zero_stuff_mode 1[3] 0 +mix_mode 1[2] 1 complex, real +modulation_form 1[1] 0 e_minus_jwt, e_plus_jwt +data_clk_pll_lock_sel 1[0] 0 pll_lock, data_clk +######################################################################## +## address 2 +######################################################################## +signed_input_data 2[7] 0 signed, unsigned +two_port_mode 2[6] 0 two_port, one_port +dataclk_driver_strength 2[5] 0 weak, strong +dataclk_invert 2[4] 0 +oneportclk_invert 2[2] 0 +iqsel_invert 2[1] 0 +iq_first 2[0] 0 i_first, q_first +######################################################################## +## address 3 +######################################################################## +data_rate_clock_output 3[7] 0 pll_lock, spi_sdo +pll_divide_ratio 3[0:1] 0 div1, div2, div4, div8 +######################################################################## +## address 4 +######################################################################## +pll_state 4[7] 0 off, on +auto_cp_control 4[6] 0 auto, manual +pll_cp_control 4[0:2] 0 50ua=0, 100ua=1, 200ua=2, 400ua=3, 800ua=7 +######################################################################## +## address 5 and 9 +######################################################################## +idac_fine_gain_adjust 5[0:7] 0 +qdac_fine_gain_adjust 9[0:7] 0 +######################################################################## +## address 6 and A +######################################################################## +idac_coarse_gain_adjust 6[0:3] 0 +qdac_coarse_gain_adjust 0xA[0:3] 0 +######################################################################## +## address 7, 8 and B, C +######################################################################## +idac_offset_adjust_msb 7[0:7] 0 +idac_offset_adjust_lsb 8[0:1] 0 +~idac_offset_adjust idac_offset_adjust_lsb, idac_offset_adjust_msb +idac_ioffset_direction 8[7] 0 out_a, out_b +qdac_offset_adjust_msb 0xB[0:7] 0 +qdac_offset_adjust_lsb 0xC[0:1] 0 +~qdac_offset_adjust qdac_offset_adjust_lsb, qdac_offset_adjust_msb +qdac_ioffset_direction 0xC[7] 0 out_a, out_b +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return reg; +} + +boost::uint16_t get_write_reg(boost::uint8_t addr){ + return (boost::uint16_t(addr) << 8) | get_reg(addr); +} + +boost::uint16_t get_read_reg(boost::uint8_t addr){ + return (boost::uint16_t(addr) << 8) | (1 << 7); +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='ad9777_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_ad9862_regs.py b/host/lib/ic_reg_maps/gen_ad9862_regs.py new file mode 100755 index 000000000..00340224c --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad9862_regs.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## General +######################################################################## +sdio_bidir 0[7] 0 sdio_sdo, sdio +lsb_first 0[6] 0 msb, lsb +soft_reset 0[5] 0 +######################################################################## +## Rx Power Down +######################################################################## +vref_diff_pd 1[7] 0 +vref_pd 1[6] 0 +rx_digital_pd 1[5] 0 +rx_channel_b_pd 1[4] 0 +rx_channel_a_pd 1[3] 0 +buffer_b_pd 1[2] 0 +buffer_a_pd 1[1] 0 +all_rx_pd 1[0] 0 +######################################################################## +## Rx A and B +######################################################################## +#for $x, $i in (('a', 2), ('b', 3)) +byp_buffer_$x $(i)[7] 0 +rx_pga_$x $(i)[0:4] 0 +#end for +######################################################################## +## Rx Misc +######################################################################## +hs_duty_cycle 4[2] 0 +shared_ref 4[1] 0 +clk_duty 4[0] 0 +######################################################################## +## RX I/F (INTERFACE) +######################################################################## +three_state 5[4] 0 +rx_retime 5[3] 0 clkout1, clkout2 +rx_twos_comp 5[2] 0 +inv_rxsync 5[1] 0 +mux_out 5[0] 0 rx_mux_mode=1, dual_port_mode=0 +######################################################################## +## RX Digital +######################################################################## +two_channel 6[3] 1 rx_b_dis, both_enb +rx_keep_ve 6[2] 0 pass_pos, pass_neg +rx_hilbert 6[1] 0 dis, enb +decimate 6[0] 0 dis, enb +######################################################################## +## TX Power Down +######################################################################## +alt_timing_mode 8[5] 0 +txoff_enable 8[4] 0 +tx_digital_pd 8[3] 0 +tx_analog_pd 8[0:2] 0 none=0, txb=4, txa=2, both=7 +######################################################################## +## Tx Offset and Gain +######################################################################## +#for $x, $i, $j, $k in (('a', 10, 11, 14), ('b', 12, 13, 15)) +dac_$(x)_offset_1_0 $(i)[6:7] 0 +dac_$(x)_offset_dir $(i)[0] 0 neg_diff, pos_dif +dac_$(x)_offset_9_2 $(j)[0:7] 0 +dac_$(x)_coarse_gain $(k)[6:7] 0 +dac_$(x)_fine_gain $(k)[0:5] 0 +#end for +tx_pga_gain 16[0:7] 0 +######################################################################## +## Tx Misc +######################################################################## +tx_slave_enable 17[1] 0 +tx_pga_mode 17[0] 0 normal, fast +######################################################################## +## Tx IF (INTERFACE) +######################################################################## +tx_retime 18[6] 1 clkout1=1, clkout2=0 +qi_order 18[5] 0 iq, qi +inv_txsync 18[4] 0 +tx_twos_comp 18[3] 0 +inverse_samp 18[2] 0 rise, fall +edges 18[1] 0 normal, both +interleaved 18[0] 0 single, interleaved +######################################################################## +## TX Digital +######################################################################## +two_data_paths 19[4] 0 single, both +tx_keep_ve 19[3] 0 pass_pos, pass_neg +tx_hilbert 19[2] 0 dis, enb +interp 19[0:1] 0 1, 2, 4 +######################################################################## +## TX Modulator +######################################################################## +neg_fine_tune 20[5] 0 pos_shift, neg_shift +fine_mode 20[4] 0 bypass, nco +real_mix_mode 20[3] 0 complex, real +neg_coarse_tune 20[2] 0 pos_shift, neg_shift +coarse_mod 20[0:1] 0 bypass, fdac_4, fdac_8 +######################################################################## +## NCO Tuning Word +######################################################################## +ftw_7_0 21[0:7] 0 +ftw_15_8 22[0:7] 0 +ftw_23_16 23[0:7] 0 +######################################################################## +## DLL +######################################################################## +input_clk_ctrl 24[6] 0 internal, external +adc_div2 24[5] 0 normal, div2 +dll_mult 24[3:4] 0 1, 2, 4 +dll_pd 24[2] 0 +dll_mode 24[0] 0 slow, fast +######################################################################## +## Clock Out +######################################################################## +clkout2_div_factor 25[6:7] 0 1, 2, 4, 8 +inv2 25[5] 0 normal, inverted +inv1 25[1] 0 normal, inverted +dis2 25[4] 0 enb, dis +dis1 25[0] 0 enb, dis +######################################################################## +## Aux ADC +######################################################################## +#for $x, $i in (('a2', 26), ('a1', 28), ('b2', 30), ('b1', 32)) +aux_adc_$(x)_1_0 $(i)[6:7] 0 +aux_adc_$(x)_9_2 $int(1+$i)[0:7] 0 +#end for +######################################################################## +## Aux ADC Control +######################################################################## +aux_spi 34[7] 0 dis, enb +sel_bnota 34[6] 0 adc_a, adc_b +#for $x, $i in (('b', 5), ('a', 2)) +refsel_$(x) 34[$i] 0 external, internal +select_$(x) 34[$int($i-1)] 0 aux_adc2, aux_adc1 +start_$(x) 34[$int($i-2)] 0 +#end for +######################################################################## +## Aux ADC Clock +######################################################################## +clk_4 35[0] 0 1_2, 1_4 +######################################################################## +## Aux DAC +######################################################################## +#for $x, $i in (('a', 36), ('b', 37), ('c', 38)) +aux_dac_$x $(i)[0:7] 0 +#end for +######################################################################## +## Aux DAC Update +######################################################################## +aux_dac_slave_enable 39[7] 0 +aux_dacupdate_c 39[2] 0 +aux_dacupdate_b 39[1] 0 +aux_dacupdate_a 39[0] 0 +######################################################################## +## AUX DAC Power Down +######################################################################## +aux_dac_pd_a 40[2] 0 +aux_dac_pd_b 40[1] 0 +aux_dac_pd_c 40[0] 0 +######################################################################## +## AUX DAC Control +######################################################################## +aux_dac_invert_a 41[2] 0 +aux_dac_invert_b 41[1] 0 +aux_dac_invert_c 41[0] 0 +######################################################################## +## Sig Delt +######################################################################## +sig_delt_3_0 42[4:7] 0 +sig_delt_11_4 43[0:7] 0 +######################################################################## +## ADC Low Power +######################################################################## +rx_low_power_mode_r49 49[0:7] 0 +rx_low_power_mode_r50 50[0:7] 0 +######################################################################## +## Chip ID +######################################################################## +chip_id 63[0:7] 0 +""" + +######################################################################## +# Header and Source templates below +######################################################################## +BODY_TMPL=""" +boost::uint8_t get_reg(boost::uint8_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in range(0, 63+1) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint16_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return reg; +} + +void set_reg(boost::uint8_t addr, boost::uint16_t reg){ + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); + #end for + break; + #end for + } +} + +boost::uint16_t get_write_reg(boost::uint8_t addr){ + return (boost::uint16_t(addr) << 8) | get_reg(addr); +} + +boost::uint16_t get_read_reg(boost::uint8_t addr){ + return (boost::uint16_t(addr) << 8) | (1 << 15); +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='ad9862_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_adf4350_regs.py b/host/lib/ic_reg_maps/gen_adf4350_regs.py new file mode 100755 index 000000000..fce2f569b --- /dev/null +++ b/host/lib/ic_reg_maps/gen_adf4350_regs.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +frac_12_bit 0[3:14] 0 +int_16_bit 0[15:30] 0x23 +##reserved 0[31] 0 +######################################################################## +## address 1 +######################################################################## +mod_12_bit 1[3:14] 0xfff +phase_12_bit 1[15:26] 0 +prescaler 1[27] 0 4_5, 8_9 +##reserved 1[28:31] 0 +######################################################################## +## address 2 +######################################################################## +counter_reset 2[3] 0 disabled, enabled +cp_three_state 2[4] 0 disabled, enabled +power_down 2[5] 0 disabled, enabled +pd_polarity 2[6] 1 negative, positive +ldp 2[7] 0 10ns, 6ns +ldf 2[8] 0 frac_n, int_n +#set $current_setting_enums = ', '.join(map(lambda x: '_'.join(("%0.2fma"%(round(x*31.27 + 31.27)/100)).split('.')), range(0,16))) +charge_pump_current 2[9:12] 5 $current_setting_enums +double_buffer 2[13] 0 disabled, enabled +r_counter_10_bit 2[14:23] 0 +reference_divide_by_2 2[24] 1 disabled, enabled +reference_doubler 2[25] 0 disabled, enabled +muxout 2[26:28] 1 3state, dvdd, dgnd, rdiv, ndiv, analog_ld, dld, reserved +low_noise_and_spur 2[29:30] 3 low_noise, reserved0, reserved1, low_spur +##reserved 2[31] 0 +######################################################################## +## address 3 +######################################################################## +clock_divider_12_bit 3[3:14] 0 +clock_div_mode 3[15:16] 1 clock_divider_off, fast_lock, resync_enable, reserved +##reserved 3[17] 0 +cycle_slip_reduction 3[18] 0 disabled, enabled +##reserved 3[19:20] 0 +##reserved 3[21:31] 0 +######################################################################## +## address 4 +######################################################################## +output_power 4[3:4] 3 m4dbm, m1dbm, 2dbm, 5dbm +rf_output_enable 4[5] 1 disabled, enabled +aux_output_power 4[6:7] 0 m4dbm, m1dbm, 2dbm, 5dbm +aux_output_enable 4[8] 0 disabled, enabled +aux_output_select 4[9] 1 divided, fundamental +mute_till_lock_detect 4[10] 0 mute_disabled, mute_enabled +vco_power_down 4[11] 0 vco_powered_up, vco_powered_down +band_select_clock_div 4[12:19] 0 +rf_divider_select 4[20:22] 0 div1, div2, div4, div8, div16 +feedback_select 4[23] 1 divided, fundamental +##reserved 4[24:31] 0 +######################################################################## +## address 5 +######################################################################## +##reserved 5[3:18] 0 +##reserved 5[19:20] 0 +##reserved 5[21] 0 +ld_pin_mode 5[22:23] 1 low0, dld, low, high +##reserved 5[24:31] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +enum addr_t{ + ADDR_R0 = 0, + ADDR_R1 = 1, + ADDR_R2 = 2, + ADDR_R3 = 3, + ADDR_R4 = 4, + ADDR_R5 = 5 +}; + +boost::uint32_t get_reg(boost::uint8_t addr){ + boost::uint32_t reg = addr & 0x7; + switch(addr){ + #for $addr in range(5+1) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return reg; +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='adf4350_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_adf4351_regs.py b/host/lib/ic_reg_maps/gen_adf4351_regs.py new file mode 100755 index 000000000..607b2979d --- /dev/null +++ b/host/lib/ic_reg_maps/gen_adf4351_regs.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +frac_12_bit 0[3:14] 0 +int_16_bit 0[15:30] 0x23 +##reserved 0[31] 0 +######################################################################## +## address 1 +######################################################################## +mod_12_bit 1[3:14] 0xfff +phase_12_bit 1[15:26] 0 +prescaler 1[27] 0 4_5, 8_9 +phase_adjust 1[28] 0 +##reserved 1[29:31] 0 +######################################################################## +## address 2 +######################################################################## +counter_reset 2[3] 0 disabled, enabled +cp_three_state 2[4] 0 disabled, enabled +power_down 2[5] 0 disabled, enabled +pd_polarity 2[6] 1 negative, positive +ldp 2[7] 0 10ns, 6ns +ldf 2[8] 0 frac_n, int_n +#set $current_setting_enums = ', '.join(map(lambda x: '_'.join(("%0.2fma"%(round(x*31.27 + 31.27)/100)).split('.')), range(0,16))) +charge_pump_current 2[9:12] 5 $current_setting_enums +double_buffer 2[13] 0 disabled, enabled +r_counter_10_bit 2[14:23] 0 +reference_divide_by_2 2[24] 1 disabled, enabled +reference_doubler 2[25] 0 disabled, enabled +muxout 2[26:28] 1 3state, dvdd, dgnd, rdiv, ndiv, analog_ld, dld, reserved +low_noise_and_spur 2[29:30] 3 low_noise, reserved0, reserved1, low_spur +##reserved 2[31] 0 +######################################################################## +## address 3 +######################################################################## +clock_divider_12_bit 3[3:14] 0 +clock_div_mode 3[15:16] 0 clock_divider_off, fast_lock, resync_enable, reserved +##reserved 3[17] 0 +cycle_slip_reduction 3[18] 0 disabled, enabled +##reserved 3[19:20] 0 +charge_cancel 3[21] 0 +anti_backlash_pulse 3[22] 0 6ns, 3ns +band_select_mode 3[23] 0 low, high +##reserved 3[24:31] 0 +######################################################################## +## address 4 +######################################################################## +output_power 4[3:4] 3 m4dbm, m1dbm, 2dbm, 5dbm +rf_output_enable 4[5] 1 disabled, enabled +aux_output_power 4[6:7] 0 m4dbm, m1dbm, 2dbm, 5dbm +aux_output_enable 4[8] 0 disabled, enabled +aux_output_select 4[9] 1 divided, fundamental +mute_till_lock_detect 4[10] 0 mute_disabled, mute_enabled +vco_power_down 4[11] 0 vco_powered_up, vco_powered_down +band_select_clock_div 4[12:19] 0 +rf_divider_select 4[20:22] 5 div1, div2, div4, div8, div16, div32, div64 +feedback_select 4[23] 1 divided, fundamental +##reserved 4[24:31] 0 +######################################################################## +## address 5 +######################################################################## +##reserved 5[3:18] 0 +##reserved 5[19:20] 0 +##reserved 5[21] 0 +ld_pin_mode 5[22:23] 1 low0, dld, low, high +##reserved 5[24:31] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +enum addr_t{ + ADDR_R0 = 0, + ADDR_R1 = 1, + ADDR_R2 = 2, + ADDR_R3 = 3, + ADDR_R4 = 4, + ADDR_R5 = 5 +}; + +boost::uint32_t get_reg(boost::uint8_t addr){ + boost::uint32_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return reg; +} + +void set_reg(boost::uint8_t addr, boost::uint32_t reg){ + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); + #end for + break; + #end for + } +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='adf4351_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_adf4360_regs.py b/host/lib/ic_reg_maps/gen_adf4360_regs.py new file mode 100755 index 000000000..3fd8707a7 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_adf4360_regs.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +core_power_level 0[2:3] 0 5ma, 10ma, 15ma, 20ma +counter_operation 0[4] 0 normal, reset +muxout_control 0[5:7] 0 3state, dld, ndiv, dvdd, rdiv, nchan_od_ld, sdo, dgnd +phase_detector_polarity 0[8] 0 neg, pos +charge_pump_output 0[9] 0 normal, 3state +cp_gain_0 0[10] 0 set1, set2 +mute_till_ld 0[11] 0 dis, enb +output_power_level 0[12:13] 0 3_5ma, 5_0ma, 7_5ma, 11_0ma +#set $current_setting_enums = ', '.join(map(lambda x: x+"ma", "0_31 0_62 0_93 1_25 1_56 1_87 2_18 2_50".split())) +current_setting1 0[14:16] 0 $current_setting_enums +current_setting2 0[17:19] 0 $current_setting_enums +power_down 0[20:21] 0 normal_op=0, async_pd=1, sync_pd=3 +prescaler_value 0[22:23] 0 8_9, 16_17, 32_33 +######################################################################## +## address 2 +######################################################################## +a_counter 2[2:6] 0 +b_counter 2[8:20] 0 +cp_gain_1 2[21] 0 set1, set2 +divide_by_2_output 2[22] 0 fund, div2 +divide_by_2_prescaler 2[23] 0 fund, div2 +######################################################################## +## address 1 +######################################################################## +r_counter 1[2:15] 0 +ablpw 1[16:17] 0 3_0ns, 1_3ns, 6_0ns +lock_detect_precision 1[18] 0 3cycles, 5cycles +test_mode_bit 1[19] 0 +band_select_clock_div 1[20:21] 0 1, 2, 4, 8 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +enum addr_t{ + ADDR_CONTROL = 0, + ADDR_NCOUNTER = 2, + ADDR_RCOUNTER = 1 +}; + +boost::uint32_t get_reg(addr_t addr){ + boost::uint32_t reg = addr & 0x3; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return reg; +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='adf4360_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_ads62p44_regs.py b/host/lib/ic_reg_maps/gen_ads62p44_regs.py new file mode 100755 index 000000000..f0a84d940 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ads62p44_regs.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +reset 0[1] 0 +serial_readout 0[0] 0 +######################################################################## +## address 16 +######################################################################## +clkout_strength 16[6:7] 0 weaker=1, default=0, stronger=3 +######################################################################## +## address 17 +######################################################################## +dataout_strength 17[0:1] 0 weaker=1, default=0, stronger=3, maximum=2 +lvds_current 17[2:3] 0 3_5ma, 2_5ma, 4_5ma, 1_75ma +lvds_current_double 17[4:5] 0 default, dblclk, dbldataclock +######################################################################## +## address 18 +######################################################################## +lvds_clk_term 18[0:2] 0 none, 300, 180, 110, 150, 100, 81, 60 +lvds_data_term 18[3:5] 0 none, 300, 180, 110, 150, 100, 81, 60 +######################################################################## +## address 19 +######################################################################## +offset_freeze 19[4] 0 +######################################################################## +## address 20 +######################################################################## +power_down 20[0:2] 0 normal, a_dis, b_dis, ab_dis, global_pd, a_sby, b_sby, mux +ref_select 20[3] 0 internal, external +coarse_gain 20[4] 0 0db, 3_5db +output_interface 20[5] 0 cmos, lvds +override 20[7] 0 +######################################################################## +## address 22 +######################################################################## +test_patterns 22[0:2] 0 normal, zeros, ones, toggle, ramp, custom +lvds_bytewise 22[3] 0 +data_format 22[4] 0 twos_complement, binary +######################################################################## +## address 23 +######################################################################## +fine_gain 23[0:3] 0 +######################################################################## +## address 24 and 25 +######################################################################## +custom_low 24[0:7] 0 +custom_high 25[0:5] 0 +######################################################################## +## address 26 +######################################################################## +gain_correction 26[0:3] 0 +offset_tc 26[4:6] 0 1_1s, 0_55s, 0_27s, 0_13s, 2_15s, 4_3s +low_latency 26[7] 0 +######################################################################## +## address 27 +######################################################################## +decimation 27[0:2] 0 decimate_2, decimate_4, decimate_1, decimate_8 +odd_tap_enable 27[3] 0 +filter_enable 27[4] 0 +filter_coeff_sel 27[5] 0 predefined, userdefined +offset_enable 27[7] 0 +######################################################################## +## address 29 +######################################################################## +decimation_filter_bands 29[0:1] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return reg; +} + +boost::uint16_t get_write_reg(boost::uint8_t addr){ + return (boost::uint16_t(addr) << 8) | get_reg(addr); +} + +boost::uint16_t get_read_reg(boost::uint8_t addr){ + return (boost::uint16_t(addr) << 8) | (1 << 7); +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='ads62p44_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_max2112_regs.py b/host/lib/ic_reg_maps/gen_max2112_regs.py new file mode 100755 index 000000000..c2fc4e3e2 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_max2112_regs.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing write registers +# name addr[bit range inclusive] default optional enums +######################################################################## +WRITE_REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## +######################################################################## +## N-Divider MSB (0) Write +######################################################################## +frac 0[7] 1 invalid, frac +n_divider_msb 0[0:6] 0 +######################################################################## +## N-Divider LSB (1) Write +######################################################################## +n_divider_lsb 1[0:7] 0x23 +~n_divider n_divider_lsb, n_divider_msb +######################################################################## +## Charge Pump (2) Write +######################################################################## +cpmp 2[6:7] 0 +cplin 2[4:5] 1 +f_divider_mmsb 2[0:3] 0x2 +######################################################################## +## F-Divider MSB (3) Write +######################################################################## +f_divider_msb 3[0:7] 0xF6 +######################################################################## +## F-Divider LSB (4) Write +######################################################################## +f_divider_lsb 4[0:7] 0x84 +~f_divider f_divider_lsb, f_divider_msb, f_divider_mmsb +######################################################################## +## XTAL-Divider R-Divider (5) Write +######################################################################## +#set $xtal_divider_names = ', '.join(map(lambda x: 'div' + str(x), range(1,9))) +xtal_divider 5[5:7] 0 $xtal_divider_names +r_divider 5[0:4] 1 +######################################################################## +## PLL (6) Write +######################################################################## +d24 6[7] 1 div2, div4 ## div2 for LO <= 1125M, div4 > 1125M +cps 6[6] 1 i_cp_from_icp, i_cp_from_vas +icp 6[5] 0 i_cp_600ua, i_cp_1200ua +##reserved 6[0:4] 0 +######################################################################## +## VCO (7) Write +######################################################################## +vco 7[3:7] 0x19 +vas 7[2] 1 disabled, enabled +adl 7[1] 1 disabled, enabled +ade 7[0] 1 disabled, enabled +######################################################################## +## LPF (8) Write +######################################################################## +lp 8[0:7] 0x4B ## map(lambda x: "%0.2f"%((4e6 + (x - 12) * 290e3)/1e6), range(255)) in MHz +######################################################################## +## Control (9) Write +######################################################################## +stby 9[7] 0 normal, disable_sig_and_synth +##reserved 9[6] 0 +pwdn 9[5] 0 normal, invalid +##reserved 9[4] 0 +bbg 9[0:3] 0 ## Baseband Gain in dB +######################################################################## +## Shutdown (0xA) Write +######################################################################## +##reserved 0xA[7] 0 +pll_shutdown 0xA[6] 0 normal, shutdown +div_shutdown 0xA[5] 0 normal, shutdown +vco_shutdown 0xA[4] 0 normal, shutdown +bb_shutdown 0xA[3] 0 normal, shutdown +rfmix_shutdown 0xA[2] 0 normal, shutdown +rfvga_shutdown 0xA[1] 0 normal, shutdown +fe_shutdown 0xA[0] 0 normal, shutdown +######################################################################## +## Test (0xB) Write +######################################################################## +cptst 0xB[5:7] 0 +##reserved 0xB[4] 0 +turbo 0xB[3] 1 +ld_mux 0xB[0:2] 0 refout=0, invalid +""" + +######################################################################## +# Template for raw text data describing read registers +# name addr[bit range inclusive] default optional enums +######################################################################## +READ_REGS_TMPL="""\ +######################################################################## +## Status Byte-1 (0xC) Read +######################################################################## +por 0xC[7] 0 read, reset +vasa 0xC[6] 0 vas_fail, vas_win +vase 0xC[5] 0 active, inactive +ld 0xC[4] 0 unlocked, locked +##reserved 0xC[0:3] 0 +######################################################################## +## Status Byte-2 (0xD) Read +######################################################################## +vcosbr 0xD[3:7] 0 ## vco band readback +adc 0xD[0:2] 0 ool0, lock0, vaslock0, vaslock1, vaslock2, vaslock3, lock1, ool1 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return boost::uint8_t(reg); +} + +void set_reg(boost::uint8_t addr, boost::uint8_t reg){ + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); + #end for + break; + #end for + } +} +""" + +SPLIT_REGS_HELPER_TMPL="""\ +#for $divname in ['n','f'] +void set_$(divname)_divider(boost::uint32_t $divname){ + #for $regname in sorted(map(lambda r: r.get_name(), filter(lambda r: r.get_name().find(divname + '_divider') == 0, $regs))) + #end for +} +#end for +""" + #$regname = boost::uint8_t($divname & $regs[regname].get_mask()); + #$divname = boost::uint32_t($divname >> $regs[regname].get_shift()); + +if __name__ == '__main__': + import common; common.generate( + name='max2112_write_regs', + regs_tmpl=WRITE_REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) + + import common; common.generate( + name='max2112_read_regs', + regs_tmpl=READ_REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + append=True, + ) diff --git a/host/lib/ic_reg_maps/gen_max2118_regs.py b/host/lib/ic_reg_maps/gen_max2118_regs.py new file mode 100755 index 000000000..506fbaec8 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_max2118_regs.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing write registers +# name addr[bit range inclusive] default optional enums +######################################################################## +WRITE_REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## +######################################################################## +## N-Divider MSB (0) Write +######################################################################## +div2 0[7] 0 div4, div2 +n_divider_msb 0[0:6] 3 +######################################################################## +## N-Divider LSB (1) Write +######################################################################## +n_divider_lsb 1[0:7] 0xB6 +~n_divider n_divider_lsb, n_divider_msb +######################################################################## +## R, Charge Pump, and VCO (2) Write +######################################################################## +#set $r_divider_names = ', '.join(map(lambda x: 'div' + str(2**(x+1)), range(0,8))) +r_divider 2[5:7] 1 $r_divider_names +#set $cp_current_bias = ', '.join(map(lambda x: 'i_cp_%dua'%(50*2**x), range(0,4))) +cp_current 2[3:4] 3 $cp_current_bias +osc_band 2[0:2] 5 +######################################################################## +## I/Q Filter DAC (3) Write +######################################################################## +##unused 3[7] 0 +f_dac 3[0:6] 0x7F ## filter tuning dac, depends on m +######################################################################## +## LPF Divider DAC (4) Write +######################################################################## +adl_vco_adc_latch 4[7] 0 disabled, enabled +ade_vco_ade_read 4[6] 0 disabled, enabled +dl_output_drive 4[5] 0 iq_590m_vpp, iq_1_vpp +m_divider 4[0:4] 2 ## filter tuning counter +######################################################################## +## GC2 and Diag (5) Write +######################################################################## +diag 5[5:7] 0 normal, cp_i_source, cp_i_sink, cp_high_z, unused, n_and_filt, r_and_gc2, m_div +gc2 5[0:4] 0x1F ## Step Size: 0-1: 0dB, 2-22: 1dB, 23-31: 0.5dB +""" + +######################################################################## +# Template for raw text data describing read registers +# name addr[bit range inclusive] default optional enums +######################################################################## +READ_REGS_TMPL="""\ +######################################################################## +## Status (0) Read +######################################################################## +pwr 0[6] 0 not_reset, reset +adc 0[2:4] 0 ## VCO tuning voltage, Lock Status +######################################################################## +## I/Q Filter DAC (1) Read +######################################################################## +filter_dac 1[0:6] 0 ## I/Q Filter tuning DAC, current +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return boost::uint8_t(reg); +} + +void set_reg(boost::uint8_t addr, boost::uint8_t reg){ + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); + #end for + break; + #end for + } +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='max2118_write_regs', + regs_tmpl=WRITE_REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) + + import common; common.generate( + name='max2118_read_regs', + regs_tmpl=READ_REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + append=True, + ) diff --git a/host/lib/ic_reg_maps/gen_max2829_regs.py b/host/lib/ic_reg_maps/gen_max2829_regs.py new file mode 100755 index 000000000..383131c18 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_max2829_regs.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## +######################################################################## +## Standby (2) +######################################################################## +_set_to_1_2_0 2[0] 1 +_set_to_1_2_1 2[1] 1 +_set_to_1_2_2 2[2] 1 +pa_bias_dac 2[10] 0 +voltage_ref 2[11] 0 +_set_to_1_2_12 2[12] 1 +mimo_select 2[13] 0 normal, mimo +######################################################################## +## Integer Divider Ratio (3) +######################################################################## +int_div_ratio_word 3[0:7] 0xa2 +frac_div_ratio_lsb 3[12:13] 0 +######################################################################## +## Fractional Divider Ratio (4) +######################################################################## +frac_div_ratio_msb 4[0:13] 0 +######################################################################## +## Band Select and PLL (5) +######################################################################## +band_select 5[0] 0 2_4ghz, 5ghz +ref_divider 5[1:3] 1 +pll_cp_select 5[5] 1 2ma, 4ma +band_select_802_11a 5[6] 0 4_9ghz_to_5_35ghz, 5_47ghz_to_5_875ghz +vco_bandswitch 5[7] 0 disable, automatic +vco_spi_bandswitch 5[8] 0 fsm, spi +vco_sub_band 5[9:10] 0 +_set_to_1_5_11 5[11] 1 +_set_to_1_5_12 5[12] 1 +band_sel_mimo 5[13] 0 normal, mimo +######################################################################## +## Calibration (6) +######################################################################## +rx_cal_mode 6[0] 0 dis, enb +tx_cal_mode 6[1] 0 dis, enb +_set_to_1_6_10 6[10] 1 +iq_cal_gain 6[11:12] 3 8db, 18db, 24db, 34db +######################################################################## +## Lowpass Filter (7) +######################################################################## +rx_lpf_fine_adj 7[0:2] 2 90, 95, 100, 105, 110 +rx_lpf_coarse_adj 7[3:4] 1 7_5mhz, 9_5mhz, 14mhz, 18mhz +tx_lpf_coarse_adj 7[5:6] 1 12mhz=1, 18mhz=2, 24mhz=3 +rssi_high_bw 7[11] 0 2mhz, 6mhz +######################################################################## +## Rx Control/RSSI (8) +######################################################################## +_set_to_1_8_0 8[0] 1 +rx_highpass 8[2] 1 100hz, 30khz +_set_to_1_8_5 8[5] 1 +rssi_pin_fcn 8[8] 0 rssi, temp +rssi_op_mode 8[10] 0 rssi_rxhp, enabled +rssi_output_range 8[11] 0 low, high +rx_vga_gain_spi 8[12] 0 io, spi +######################################################################## +## Tx Linearity/Baseband Gain (9) +######################################################################## +tx_baseband_gain 9[0:1] 0 0db, 2db, 3_5db, 5db +tx_upconv_linearity 9[2:3] 0 50, 63, 78, 100 +tx_vga_linearity 9[6:7] 0 50, 63, 78, 100 +pa_driver_linearity 9[8:9] 2 50, 63, 78, 100 +tx_vga_gain_spi 9[10] 0 io, spi +######################################################################## +## PA Bias DAC (10) +######################################################################## +pa_bias_dac_out_curr 10[0:5] 0 +pa_bias_dac_delay 10[6:9] 0xf +######################################################################## +## Rx Gain (11) +######################################################################## +rx_vga_gain 11[0:4] 0x1f +rx_lna_gain 11[5:6] 3 +######################################################################## +## Tx VGA Gain (12) +######################################################################## +tx_vga_gain 12[0:5] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint32_t get_reg(boost::uint8_t addr){ + boost::uint16_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint16_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return (boost::uint32_t(reg) << 4) | (addr & 0xf); +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='max2829_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_tda18272hnm_regs.py b/host/lib/ic_reg_maps/gen_tda18272hnm_regs.py new file mode 100755 index 000000000..677a201de --- /dev/null +++ b/host/lib/ic_reg_maps/gen_tda18272hnm_regs.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing write registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## +######################################################################## +## ID_byte_1 (0x00) Read +######################################################################## +##reserved as 1 0x00[7] 1 +ident_14_8 0x00[0:6] 0 +######################################################################## +## ID_byte_2 (0x01) Read +######################################################################## +ident_7_0 0x01[0:7] 0 +##~ident ident_7_0, ident_14_8 +######################################################################## +## ID_byte_3 (0x02) Read +######################################################################## +major_rev 0x02[4:7] 0 +minor_rev 0x02[0:3] 0 +######################################################################## +## Thermo_byte_1 (0x03) Read +######################################################################## +##reserved 0x03[7] 0 +tm_d 0x03[0:6] 0 ## 22-127deg C junction temp +######################################################################## +## Thermo_byte_2 (0x04) Write +######################################################################## +##reserved 0x04[1:7] 0 +tm_on 0x04[0] 0 sensor_off, sensor_on +######################################################################## +## Power_state_byte_1 (0x05) Read +######################################################################## +##reserved 0x05[2:7] 0 +por 0x05[1] 0 read, reset +lo_lock 0x05[0] 0 unlocked, locked +######################################################################## +## Power_state_byte_2 (0x06) Read/Write ## Standby modes +######################################################################## +##reserved 0x06[4:7] 0 +sm 0x06[3] 0 normal, standby +sm_pll 0x06[2] 0 on, off +sm_lna 0x06[1] 0 on, off +##resevered as 0 0x06[0] 0 +## (sm, sm_pll, sm_lna) only valid values are 000, 100, 110, and 111 +######################################################################## +## Input_Power_Level_byte (0x07) Read +######################################################################## +##reserved 0x07[7] 0 +power_level 0x07[0:6] 0 ## 40dB_Vrms to 110dB_Vrms +## Trigger power level calculation with MSM_byte_1 and MSM_byte_2 +######################################################################## +## IRQ_status (0x08) Read/Write +######################################################################## +irq_status 0x08[7] 0 cleared, set +##reserved 0x08[6] 0 +irq_xtalcal_end 0x08[5] 0 false, true +irq_rssi_end 0x08[4] 0 false, true +irq_localc_end 0x08[3] 0 false, true +irq_rfcal_end 0x08[2] 0 false, true +irq_ircal_end 0x08[1] 0 false, true +irq_rccal_end 0x08[0] 0 false, true +######################################################################## +## IRQ_enable (0x09) Read/Write +######################################################################## +irq_enable 0x09[7] 1 false, true +##reserved 0x09[6] 0 +irq_xtalcal_enable 0x09[5] 0 false, true +irq_rssi_enable 0x09[4] 0 false, true +irq_localc_enable 0x09[3] 0 false, true +irq_rfcal_enable 0x09[2] 0 false, true +irq_ircal_enable 0x09[1] 0 false, true +irq_rccal_enable 0x09[0] 0 false, true +######################################################################## +## IRQ_clear (0x0a) Read/Write +######################################################################## +irq_clear 0x0a[7] 0 false, true +##reserved 0x0a[6] 0 +irq_xtalcal_clear 0x0a[5] 0 false, true +irq_rssi_clear 0x0a[4] 0 false, true +irq_localc_clear 0x0a[3] 0 false, true +irq_rfcal_clear 0x0a[2] 0 false, true +irq_ircal_clear 0x0a[1] 0 false, true +irq_rccal_clear 0x0a[0] 0 false, true +######################################################################## +## IRQ_set (0x0b) Read +######################################################################## +irq_set 0x0b[7] 0 false, true +##reserved 0x0b[6] 0 +irq_xtalcal_set 0x0b[5] 0 false, true +irq_rssi_set 0x0b[4] 0 false, true +irq_localc_set 0x0b[3] 0 false, true +irq_rfcal_set 0x0b[2] 0 false, true +irq_ircal_set 0x0b[1] 0 false, true +irq_rccal_set 0x0b[0] 0 false, true +######################################################################## +## AGC1_byte_1 (0x0c) Read/Write +######################################################################## +lt_enable 0x0c[7] 0 +agc1_6_15db 0x0c[6] 1 +##reserved 0x0c[4:5] 0 +agc1_top 0x0c[0:3] 0 +######################################################################## +## AGC2_byte_1 (0x0d) Read +######################################################################## +##reserved 0x0d[5:7] 0 +agc2_top 0x0d[0:4] 0xf +######################################################################## +## AGCK_byte_1 (0x0e) Read/Write +######################################################################## +agcs_up_step_assym 0x0e[6:7] 3 +agcs_up_step 0x0e[5] 1 +pulse_shaper_disable 0x0e[4] 0 vsync_pulse, 500us_pulse +agck_step 0x0e[2:3] 0 +agck_mode 0x0e[0:1] 1 analog_tv=1, digital_tv=2 +######################################################################## +## RF_AGC_byte_1 (0x0f) Read/Write +######################################################################## +pd_rfagc_adapt 0x0f[7] 0 on, off +rfagc_adapt_top 0x0f[5:6] 0 +rfagc_low_bw 0x0f[4] 1 +rf_atten_3db 0x0f[3] 0 0db, 3db ## FIXME +agc3_top 0x0f[0:2] 1 +######################################################################## +## IR_MIXER_byte_1 (0x10) Read/Write +######################################################################## +##reserved 0x10[4:7] 0 +agc4_top 0x10[0:3] 1 +######################################################################## +## AGC5_byte_1 (0x11) Read/Write +######################################################################## +##reserved 0x11[7] 0 +agcs_do_step_assym 0x11[5:6] 2 +agc5_hpf 0x11[4] 1 off, on +agc5_top 0x11[0:3] 1 +######################################################################## +## IF_AGC_byte (0x12) Read/Write +######################################################################## +##reserved 0x12[3:7] 0 +if_level 0x12[0:2] 0 0_5vpp=7, 0_6vpp=6, 0_7vpp=5, 0_85vpp=4, 0_8vpp=3, 1_0vpp=2, 1_25vpp=1, 2_0vpp=0 +######################################################################## +## IF_byte_1 (0x13) Read/Write +######################################################################## +if_hp_fc 0x13[6:7] 0 0_4mhz, 0_85mhz, 1_0mhz, 1_5mhz +if_atsc_notch 0x13[5] 0 off, on +lp_fc_offset 0x13[3:4] 0 0_percent, m4_percent, m8_percent, forbidden +lp_fc 0x13[0:2] 3 1_7mhz=4, 6_0mhz=0, 7_0mhz=1, 8_0mhz=2, 10_0mhz=3 +######################################################################## +## Reference_byte (0x14) Read/Write +######################################################################## +i2c_clock_mode 0x14[7] 0 +digital_clock 0x14[6] 1 spread_off, spread_on +##reserved 0x14[5] 0 +xtalosc_anareg_en 0x14[4] 0 +##reserved 0x14[2:3] 0 +xtout 0x14[0:1] 0 no=0, 16mhz=3 +######################################################################## +## IF_Frequency_byte (0x15) Read/Write +######################################################################## +if_freq 0x15[0:7] 0 ## IF frequency = if_freq*50 (kHz) +######################################################################## +## RF_Frequency_byte_1 (0x16) Read/Write +######################################################################## +##reserved 0x16[4:7] 0 +rf_freq_19_16 0x16[0:3] 0 +######################################################################## +## RF_Frequency_byte_2 (0x17) Read/Write +######################################################################## +rf_freq_15_8 0x17[0:7] 0 +######################################################################## +## RF_Frequency_byte_3 (0x18) Read/Write +######################################################################## +rf_freq_7_0 0x18[0:7] 0 +~rf_freq rf_freq_7_0, rf_freq_15_8, rf_freq_19_16 +## RF Frequency = rf_freq (kHz) +######################################################################## +## MSM_byte_1 (0x19) Read/Write +######################################################################## +rssi_meas 0x19[7] 0 +rf_cal_av 0x19[6] 0 +rf_cal 0x19[5] 0 +ir_cal_loop 0x19[4] 0 +ir_cal_image 0x19[3] 0 +ir_cal_wanted 0x19[2] 0 +rc_cal 0x19[1] 0 +calc_pll 0x19[0] 0 +######################################################################## +## MSM_byte_2 (0x1a) Read +######################################################################## +##reserved 0x1a[2:7] 0 +xtalcal_launch 0x1a[1] 0 +msm_launch 0x1a[0] 0 +######################################################################## +## PSM_byte_1 (0x1b) Read +######################################################################## +psm_agc1 0x1b[6:7] 0 +psm_stob 0x1b[5] 0 +psmrfpoly 0x1b[4] 0 +psm_mixer 0x1b[3] 0 +psm_ifpoly 0x1b[2] 0 +psm_lodriver 0x1b[0:1] 0 +######################################################################## +## DCC_byte_1 (0x1c) Read +######################################################################## +dcc_bypass 0x1c[7] 0 +dcc_slow 0x1c[6] 0 +dcc_psm 0x1c[5] 0 +##reserved 0x1c[0:4] 0 +######################################################################## +## FLO_Max_byte (0x1d) Read +######################################################################## +##reserved 0x1d[6:7] 0 +fmax_lo 0x1d[0:5] 0xA +######################################################################## +## IR_Cal_byte_1 (0x1e) Read +######################################################################## +ir_loop 0x1e[6:7] 0 +ir_target 0x1e[3:5] 0 +ir_gstep 0x1e[0:2] 0 +######################################################################## +## IR_Cal_byte_2 (0x1f) Read +######################################################################## +ir_corr_boost 0x1f[7] 0 +ir_freqlow_sel 0x1f[6] 0 +ir_mode_ram_store 0x1f[5] 0 +ir_freqlow 0x1f[0:4] 0 +######################################################################## +## IR_Cal_byte_3 (0x20) Read +######################################################################## +##reserved 0x20[5:7] 0 +ir_freqmid 0x20[0:4] 0 +######################################################################## +## IR_Cal_byte_4 (0x21) Read +######################################################################## +##reserved 0x21[5:7] 0 +coarse_ir_freqhigh 0x21[4] 0 +ir_freqhigh 0x21[0:3] 0 +######################################################################## +## Vsync_Mgt_byte (0x22) Read +######################################################################## +pd_vsync_mgt 0x22[7] 0 +pd_ovld 0x22[6] 0 +pd_udld 0x22[5] 0 +agc_ovld_top 0x22[2:4] 0 +agc_ovld_timer 0x22[0:1] 0 +######################################################################## +## IR_MIXER_byte_2 (0x23) Read/Write +######################################################################## +ir_mixer_loop_off 0x23[7] 0 +ir_mixer_do_step 0x23[5:6] 0 +##reserved 0x23[2:4] 0 +hi_pass 0x23[1] 0 disable, enable ## FIXME Logic Unclear +if_notch 0x23[0] 1 on, off +######################################################################## +## AGC1_byte_2 (0x24) Read +######################################################################## +agc1_loop_off 0x24[7] 0 +agc1_do_step 0x24[5:6] 2 +force_agc1_gain 0x24[4] 0 +agc1_gain 0x24[0:3] 8 +######################################################################## +## AGC5_byte_2 (0x25) Read +######################################################################## +agc5_loop_off 0x25[7] 0 +agc5_do_step 0x25[5:6] 0 +##reserved 0x25[4] 0 +force_agc5_gain 0x25[3] 0 +##reserved 0x25[2] 0 +agc5_gain 0x25[0:1] 2 +######################################################################## +## RF_Cal_byte_1 (0x26) Read +######################################################################## +rfcal_offset_cprog0 0x26[6:7] 0 +rfcal_freq0 0x26[4:5] 0 +rfcal_offset_cprog1 0x26[2:3] 0 +rfcal_freq1 0x26[0:1] 0 +######################################################################## +## RF_Cal_byte_2 (0x27) Read +######################################################################## +rfcal_offset_cprog2 0x27[6:7] 0 +rfcal_freq2 0x27[4:5] 0 +rfcal_offset_cprog3 0x27[2:3] 0 +rfcal_freq3 0x27[0:1] 0 +######################################################################## +## RF_Cal_byte_3 (0x28) Read +######################################################################## +rfcal_offset_cprog4 0x28[6:7] 0 +rfcal_freq4 0x28[4:5] 0 +rfcal_offset_cprog5 0x28[2:3] 0 +rfcal_freq5 0x28[0:1] 0 +######################################################################## +## RF_Cal_byte_4 (0x29) Read +######################################################################## +rfcal_offset_cprog6 0x29[6:7] 0 +rfcal_freq6 0x29[4:5] 0 +rfcal_offset_cprog7 0x29[2:3] 0 +rfcal_freq7 0x29[0:1] 0 +######################################################################## +## RF_Cal_byte_5 (0x2a) Read +######################################################################## +rfcal_offset_cprog8 0x2a[6:7] 0 +rfcal_freq8 0x2a[4:5] 0 +rfcal_offset_cprog9 0x2a[2:3] 0 +rfcal_freq9 0x2a[0:1] 0 +######################################################################## +## RF_Cal_byte_6 (0x2b) Read +######################################################################## +rfcal_offset_cprog10 0x2b[6:7] 0 +rfcal_freq10 0x2b[4:5] 0 +rfcal_offset_cprog11 0x2b[2:3] 0 +rfcal_freq11 0x2b[0:1] 0 +######################################################################## +## RF_Filter_byte_1 (0x2c) Read +######################################################################## +rf_filter_bypass 0x2c[7] 0 +##reserved as 0 0x2c[6] 0 +agc2_loop_off 0x2c[5] 0 +force_agc2_gain 0x2c[4] 0 +rf_filter_gv 0x2c[2:3] 2 +rf_filter_band 0x2c[0:1] 0 +######################################################################## +## RF_Filter_byte_2 (0x2d) Read +######################################################################## +rf_filter_cap 0x2d[0:7] 0 +######################################################################## +## RF_Filter_byte_3 (0x2e) Read +######################################################################## +agc2_do_step 0x2e[6:7] 2 +gain_taper 0x2e[0:5] 0 +######################################################################## +## RF_Band_Pass_Filter_byte (0x2f) Read +######################################################################## +rf_bpf_bypass 0x2f[7] 0 +##reserved 0x2f[3:6] 0 +rf_bpf 0x2f[0:2] 0 +######################################################################## +## CP_Current_byte (0x30) Read +######################################################################## +##reserved 0x30[7] 0 +n_cp_current 0x30[0:6] 0x68 +######################################################################## +## AGC_Det_Out_byte (0x31) Read +######################################################################## +up_agc5 0x31[7] 0 +do_agc5 0x31[6] 0 +up_agc4 0x31[5] 0 +do_agc4 0x31[4] 0 +up_agc2 0x31[3] 0 +do_agc2 0x31[2] 0 +up_agc1 0x31[1] 0 +do_agc1 0x31[0] 0 +######################################################################## +## RF_AGC_Gain_byte_1 (0x32) Read +######################################################################## +#set $lna_gain_names = ', '.join(map(lambda x: {0: '', 1: 'm'}[3*x-12 < 0] + str(abs(3*x-12)) + 'db=' + str(x), range(0,10))) +##reserved 0x32[6:7] 0 +agc2_gain_read 0x32[4:5] 3 m11db, m8db, m5db, m2db +agc1_gain_read 0x32[0:3] 9 $lna_gain_names +######################################################################## +## RF_AGC_Gain_byte_2 (0x33) Read +######################################################################## +#set $top_agc3_read_names = ', '.join(map(lambda x: str(int(round(1.92*x+94))) + 'dbuvrms=' + str(x), range(0,8))) +##reserved 0x33[3:7] 0 +top_agc3_read 0x33[0:2] 0 $top_agc3_read_names +######################################################################## +## IF_AGC_Gain_byte (0x34) Read +######################################################################## +#set $lpf_gain_names = ', '.join(map(lambda x: str(3*x) + 'db=' + str(x), range(0,4))) +#set $ir_mixer_names = ', '.join(map(lambda x: str(3*x+2) + 'db=' + str(x), range(0,5))) +##reserved 0x34[5:7] 0 +agc5_gain_read 0x34[3:4] 3 $lpf_gain_names +agc4_gain_read 0x34[0:2] 4 $ir_mixer_names +######################################################################## +## Power_byte_1 (0x35) Read +######################################################################## +rssi 0x35[0:7] 0 +######################################################################## +## Power_byte_2 (0x36) Read +######################################################################## +##reserved 0x36[6:7] 0 +rssi_av 0x36[5] 0 +##reserved 0x36[4] 0 +rssi_cap_reset_en 0x36[3] 1 +rssi_cap_val 0x36[2] 1 +rssi_ck_speed 0x36[1] 0 +rssi_dicho_not 0x36[0] 1 +######################################################################## +## Misc_byte_1 (0x37) Read/Write +######################################################################## +rfcal_phi2 0x37[6:7] 1 +dds_polarity 0x37[5] 0 +rfcal_deltagain 0x37[1:4] 1 +irq_polarity 0x37[0] 0 raised_vcc, raised_low +######################################################################## +## rfcal_log_1 (0x38) Read +######################################################################## +rfcal_log_1 0x38[0:7] 0 +######################################################################## +## rfcal_log_2 (0x39) Read +######################################################################## +rfcal_log_2 0x39[0:7] 0 +######################################################################## +## rfcal_log_3 (0x3a) Read +######################################################################## +rfcal_log_3 0x3a[0:7] 0 +######################################################################## +## rfcal_log_4 (0x3b) Read +######################################################################## +rfcal_log_4 0x3b[0:7] 0 +######################################################################## +## rfcal_log_5 (0x3c) Read +######################################################################## +rfcal_log_5 0x3c[0:7] 0 +######################################################################## +## rfcal_log_6 (0x3d) Read +######################################################################## +rfcal_log_6 0x3d[0:7] 0 +######################################################################## +## rfcal_log_7 (0x3e) Read +######################################################################## +rfcal_log_7 0x3e[0:7] 0 +######################################################################## +## rfcal_log_8 (0x3f) Read +######################################################################## +rfcal_log_8 0x3f[0:7] 0 +######################################################################## +## rfcal_log_9 (0x40) Read +######################################################################## +rfcal_log_9 0x40[0:7] 0 +######################################################################## +## rfcal_log_10 (0x41) Read +######################################################################## +rfcal_log_10 0x41[0:7] 0 +######################################################################## +## rfcal_log_11 (0x42) Read +######################################################################## +rfcal_log_11 0x42[0:7] 0 +######################################################################## +## rfcal_log_12 (0x43) Read +######################################################################## +rfcal_log_12 0x43[0:7] 0 +## +## +######################################################################## +## FORBIDDEN ACCESS to 0x50-0x67 and 0xFE-0xFF +######################################################################## +######################################################################## +## xtal_cal_dac (0x65) Write +######################################################################## +magic 0x43[7] 1 untouched, xtal_cal_dac +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return boost::uint8_t(reg); +} + +void set_reg(boost::uint8_t addr, boost::uint8_t reg){ + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); + #end for + break; + #end for + } +} +""" + +SPLIT_REGS_HELPER_TMPL="""\ +#for $divname in ['n','f'] +void set_$(divname)_divider(boost::uint32_t $divname){ + #for $regname in sorted(map(lambda r: r.get_name(), filter(lambda r: r.get_name().find(divname + '_divider') == 0, $regs))) + #end for +} +#end for +""" + +if __name__ == '__main__': + import common; common.generate( + name='tda18272hnm_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py b/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py new file mode 100644 index 000000000..73f7aa3db --- /dev/null +++ b/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## Divider byte 1 +######################################################################## +db1 0[0:6] 0x00 +######################################################################## +## Divider byte 2 +######################################################################## +db2 1[0:7] 0x00 +######################################################################## +## Control byte 1 +######################################################################## +cb7 2[7] 0x01 +cp 2[6] 0x00 low,high +os 2[0] 0x00 on,off +rs 2[1:2] 0x00 d512=3,d640=0,d1024=1 +test 2[3:5] 0x01 normal=0x01,cpoff=0x02,cpsink=0x06,cpsrc=0x07,cptest1=0x04,cptest2=0x05 +######################################################################## +## Control byte 2 +######################################################################## +bandsel 3[4:7] 0x03 uhf=0x03,vhfhi=0x09,vhflo=0x0a +power 3[3] 0x00 on,off +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return boost::uint8_t(reg); +} + +""" + +if __name__ == '__main__': + import common; common.generate( + name='tuner_4937di5_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/property_tree.cpp b/host/lib/property_tree.cpp new file mode 100644 index 000000000..50e82a6ba --- /dev/null +++ b/host/lib/property_tree.cpp @@ -0,0 +1,181 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/property_tree.hpp> +#include <uhd/types/dict.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/make_shared.hpp> +#include <iostream> + +using namespace uhd; + +/*********************************************************************** + * Helper function to iterate through paths + **********************************************************************/ +#include <boost/tokenizer.hpp> +#define path_tokenizer(path) \ + boost::tokenizer<boost::char_separator<char> > \ + (path, boost::char_separator<char>("/")) + +/*********************************************************************** + * Property path implementation wrapper + **********************************************************************/ +fs_path::fs_path(void): std::string(){} +fs_path::fs_path(const char *p): std::string(p){} +fs_path::fs_path(const std::string &p): std::string(p){} + +std::string fs_path::leaf(void) const{ + const size_t pos = this->rfind("/"); + if (pos == std::string::npos) return *this; + return this->substr(pos+1); +} + +fs_path fs_path::branch_path(void) const{ + const size_t pos = this->rfind("/"); + if (pos == std::string::npos) return *this; + return fs_path(this->substr(0, pos)); +} + +fs_path uhd::operator/(const fs_path &lhs, const fs_path &rhs){ + //strip trailing slash on left-hand-side + if (not lhs.empty() and *lhs.rbegin() == '/'){ + return fs_path(lhs.substr(0, lhs.size()-1)) / rhs; + } + + //strip leading slash on right-hand-side + if (not rhs.empty() and *rhs.begin() == '/'){ + return lhs / fs_path(rhs.substr(1)); + } + + return fs_path(lhs + "/" + rhs); +} + +/*********************************************************************** + * Property tree implementation + **********************************************************************/ +class property_tree_impl : public uhd::property_tree{ +public: + + property_tree_impl(const fs_path &root = fs_path()): + _root(root) + { + _guts = boost::make_shared<tree_guts_type>(); + } + + sptr subtree(const fs_path &path_) const{ + const fs_path path = _root / path_; + boost::mutex::scoped_lock lock(_guts->mutex); + + property_tree_impl *subtree = new property_tree_impl(path); + subtree->_guts = this->_guts; //copy the guts sptr + return sptr(subtree); + } + + void remove(const fs_path &path_){ + const fs_path path = _root / path_; + boost::mutex::scoped_lock lock(_guts->mutex); + + node_type *parent = NULL; + node_type *node = &_guts->root; + BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ + if (not node->has_key(name)) throw_path_not_found(path); + parent = node; + node = &(*node)[name]; + } + if (parent == NULL) throw uhd::runtime_error("Cannot uproot"); + parent->pop(fs_path(path.leaf())); + } + + bool exists(const fs_path &path_) const{ + const fs_path path = _root / path_; + boost::mutex::scoped_lock lock(_guts->mutex); + + node_type *node = &_guts->root; + BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ + if (not node->has_key(name)) return false; + node = &(*node)[name]; + } + return true; + } + + std::vector<std::string> list(const fs_path &path_) const{ + const fs_path path = _root / path_; + boost::mutex::scoped_lock lock(_guts->mutex); + + node_type *node = &_guts->root; + BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ + if (not node->has_key(name)) throw_path_not_found(path); + node = &(*node)[name]; + } + + return node->keys(); + } + + void _create(const fs_path &path_, const boost::shared_ptr<void> &prop){ + const fs_path path = _root / path_; + boost::mutex::scoped_lock lock(_guts->mutex); + + node_type *node = &_guts->root; + BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ + if (not node->has_key(name)) (*node)[name] = node_type(); + node = &(*node)[name]; + } + if (node->prop.get() != NULL) throw uhd::runtime_error("Cannot create! Property already exists at: " + path); + node->prop = prop; + } + + boost::shared_ptr<void> &_access(const fs_path &path_) const{ + const fs_path path = _root / path_; + boost::mutex::scoped_lock lock(_guts->mutex); + + node_type *node = &_guts->root; + BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ + if (not node->has_key(name)) throw_path_not_found(path); + node = &(*node)[name]; + } + if (node->prop.get() == NULL) throw uhd::runtime_error("Cannot access! Property uninitialized at: " + path); + return node->prop; + } + +private: + void throw_path_not_found(const fs_path &path) const{ + throw uhd::lookup_error("Path not found in tree: " + path); + } + + //basic structural node element + struct node_type : uhd::dict<std::string, node_type>{ + boost::shared_ptr<void> prop; + }; + + //tree guts which may be referenced in a subtree + struct tree_guts_type{ + node_type root; + boost::mutex mutex; + }; + + //members, the tree and root prefix + boost::shared_ptr<tree_guts_type> _guts; + const fs_path _root; +}; + +/*********************************************************************** + * Property tree factory + **********************************************************************/ +uhd::property_tree::sptr uhd::property_tree::make(void){ + return sptr(new property_tree_impl()); +} diff --git a/host/lib/transport/CMakeLists.txt b/host/lib/transport/CMakeLists.txt new file mode 100644 index 000000000..3cae6180a --- /dev/null +++ b/host/lib/transport/CMakeLists.txt @@ -0,0 +1,106 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Setup libusb +######################################################################## +MESSAGE(STATUS "") +FIND_PACKAGE(USB1) + +LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF) + +IF(ENABLE_USB) + MESSAGE(STATUS "USB support enabled via libusb.") + INCLUDE_DIRECTORIES(${LIBUSB_INCLUDE_DIRS}) + LIBUHD_APPEND_LIBS(${LIBUSB_LIBRARIES}) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/libusb1_control.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/libusb1_zero_copy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/libusb1_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/libusb1_base.hpp + ) +ELSE(ENABLE_USB) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/usb_dummy_impl.cpp + ) +ENDIF(ENABLE_USB) + +######################################################################## +# Setup defines for interface address discovery +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring interface address discovery...") +INCLUDE(CheckCXXSourceCompiles) +INCLUDE(CheckIncludeFileCXX) + +CHECK_CXX_SOURCE_COMPILES(" + #include <ifaddrs.h> + int main(){ + struct ifaddrs *ifap; + getifaddrs(&ifap); + return 0; + } + " HAVE_GETIFADDRS +) + +CHECK_INCLUDE_FILE_CXX(winsock2.h HAVE_WINSOCK2_H) + +IF(HAVE_GETIFADDRS) + MESSAGE(STATUS " Interface address discovery supported through getifaddrs.") + SET(IF_ADDRS_DEFS HAVE_GETIFADDRS) +ELSEIF(HAVE_WINSOCK2_H) + MESSAGE(STATUS " Interface address discovery supported through SIO_GET_INTERFACE_LIST.") + SET(IF_ADDRS_DEFS HAVE_SIO_GET_INTERFACE_LIST) +ELSE() + MESSAGE(STATUS " Interface address discovery not supported.") + SET(IF_ADDRS_DEFS HAVE_IF_ADDRS_DUMMY) +ENDIF() + +SET_SOURCE_FILES_PROPERTIES( + ${CMAKE_CURRENT_SOURCE_DIR}/if_addrs.cpp + PROPERTIES COMPILE_DEFINITIONS "${IF_ADDRS_DEFS}" +) + +######################################################################## +# Setup UDP +######################################################################## +LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_SOURCE_DIR}/udp_zero_copy.cpp) + +#On windows, the boost asio implementation uses the winsock2 library. +#Note: we exclude the .lib extension for cygwin and mingw platforms. +IF(WIN32) + LIBUHD_APPEND_LIBS(ws2_32) +ENDIF() + +######################################################################## +# Append to the list of sources for lib uhd +######################################################################## +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_vrt_if_packet.py + ${CMAKE_CURRENT_BINARY_DIR}/vrt_if_packet.cpp +) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/buffer_pool.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/if_addrs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/udp_simple.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usb_zero_copy_wrapper.cpp +) diff --git a/host/lib/transport/buffer_pool.cpp b/host/lib/transport/buffer_pool.cpp new file mode 100644 index 000000000..971bbb75a --- /dev/null +++ b/host/lib/transport/buffer_pool.cpp @@ -0,0 +1,80 @@ +// +// Copyright 2011-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/buffer_pool.hpp> +#include <boost/shared_array.hpp> +#include <vector> + +using namespace uhd::transport; + +//! pad the byte count to a multiple of alignment +static size_t pad_to_boundary(const size_t bytes, const size_t alignment){ + return bytes + (alignment - bytes)%alignment; +} + +/*********************************************************************** + * Buffer pool implementation + **********************************************************************/ +class buffer_pool_impl : public buffer_pool{ +public: + buffer_pool_impl( + const std::vector<ptr_type> &ptrs, + boost::shared_array<char> mem + ): _ptrs(ptrs), _mem(mem){ + /* NOP */ + } + + ptr_type at(const size_t index) const{ + return _ptrs.at(index); + } + + size_t size(void) const{ + return _ptrs.size(); + } + +private: + std::vector<ptr_type> _ptrs; + boost::shared_array<char> _mem; +}; + +/*********************************************************************** + * Buffer pool factor function + **********************************************************************/ +buffer_pool::sptr buffer_pool::make( + const size_t num_buffs, + const size_t buff_size, + const size_t alignment +){ + //1) pad the buffer size to be a multiple of alignment + //2) pad the overall memory size for room after alignment + //3) allocate the memory in one block of sufficient size + const size_t padded_buff_size = pad_to_boundary(buff_size, alignment); + boost::shared_array<char> mem(new char[padded_buff_size*num_buffs + alignment-1]); + + //Fill a vector with boundary-aligned points in the memory + const size_t mem_start = pad_to_boundary(size_t(mem.get()), alignment); + std::vector<ptr_type> ptrs(num_buffs); + for (size_t i = 0; i < num_buffs; i++){ + ptrs[i] = ptr_type(mem_start + padded_buff_size*i); + } + + //Create a new buffer pool implementation with: + // - the pre-computed pointers, and + // - the reference to allocated memory. + return sptr(new buffer_pool_impl(ptrs, mem)); +} + diff --git a/host/lib/transport/gen_vrt_if_packet.py b/host/lib/transport/gen_vrt_if_packet.py new file mode 100644 index 000000000..e28ce3aae --- /dev/null +++ b/host/lib/transport/gen_vrt_if_packet.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +""" +The vrt packer/unpacker code generator: + +This script will generate the pack and unpack routines that convert +metatdata into vrt headers and vrt headers into metadata. + +The generated code infers jump tables to speed-up the parsing time. +""" + +TMPL_TEXT = """ +#import time +/*********************************************************************** + * This file was generated by $file on $time.strftime("%c") + **********************************************************************/ + +\#include <uhd/exception.hpp> +\#include <uhd/transport/vrt_if_packet.hpp> +\#include <uhd/utils/byteswap.hpp> +\#include <boost/detail/endian.hpp> +\#include <vector> + +//define the endian macros to convert integers +\#ifdef BOOST_BIG_ENDIAN + \#define BE_MACRO(x) (x) + \#define LE_MACRO(x) uhd::byteswap(x) +\#else + \#define BE_MACRO(x) uhd::byteswap(x) + \#define LE_MACRO(x) (x) +\#endif + +using namespace uhd; +using namespace uhd::transport; + +typedef size_t pred_type; +typedef std::vector<pred_type> pred_table_type; +#define pred_table_index(hdr) ((hdr >> 20) & 0x1ff) + +static pred_table_type get_pred_unpack_table(void){ + pred_table_type table(1 << 9, 0); //only 9 bits useful here (20-28) + for (size_t i = 0; i < table.size(); i++){ + boost::uint32_t vrt_hdr_word = i << 20; + if(vrt_hdr_word & $hex(0x1 << 28)) table[i] |= $hex($sid_p); + if(vrt_hdr_word & $hex(0x1 << 27)) table[i] |= $hex($cid_p); + if(vrt_hdr_word & $hex(0x3 << 22)) table[i] |= $hex($tsi_p); + if(vrt_hdr_word & $hex(0x3 << 20)) table[i] |= $hex($tsf_p); + if(vrt_hdr_word & $hex(0x1 << 26)) table[i] |= $hex($tlr_p); + if(vrt_hdr_word & $hex(0x1 << 24)) table[i] |= $hex($eob_p); + if(vrt_hdr_word & $hex(0x1 << 25)) table[i] |= $hex($sob_p); + } + return table; +} + +static const pred_table_type pred_unpack_table(get_pred_unpack_table()); + +//maps trailer bits to num empty bytes +//maps num empty bytes to trailer bits +static const size_t occ_table[] = {0, 2, 1, 3}; + +######################################################################## +#def gen_code($XE_MACRO, $suffix) +######################################################################## + +void vrt::if_hdr_pack_$(suffix)( + boost::uint32_t *packet_buff, + if_packet_info_t &if_packet_info +){ + boost::uint32_t vrt_hdr_flags = 0; + + pred_type pred = 0; + if (if_packet_info.has_sid) pred |= $hex($sid_p); + if (if_packet_info.has_cid) pred |= $hex($cid_p); + if (if_packet_info.has_tsi) pred |= $hex($tsi_p); + if (if_packet_info.has_tsf) pred |= $hex($tsf_p); + if (if_packet_info.has_tlr) pred |= $hex($tlr_p); + if (if_packet_info.eob) pred |= $hex($eob_p); + if (if_packet_info.sob) pred |= $hex($sob_p); + + switch(pred){ + #for $pred in range(2**7) + case $pred: + #set $num_header_words = 1 + #set $flags = 0 + ########## Stream ID ########## + #if $pred & $sid_p + packet_buff[$num_header_words] = $(XE_MACRO)(if_packet_info.sid); + #set $num_header_words += 1 + #set $flags |= (0x1 << 28); + #end if + ########## Class ID ########## + #if $pred & $cid_p + packet_buff[$num_header_words] = 0; //not implemented + #set $num_header_words += 1 + packet_buff[$num_header_words] = 0; //not implemented + #set $num_header_words += 1 + #set $flags |= (0x1 << 27); + #end if + ########## Integer Time ########## + #if $pred & $tsi_p + packet_buff[$num_header_words] = $(XE_MACRO)(if_packet_info.tsi); + #set $num_header_words += 1 + #set $flags |= (0x3 << 22); + #end if + ########## Fractional Time ########## + #if $pred & $tsf_p + packet_buff[$num_header_words] = $(XE_MACRO)(boost::uint32_t(if_packet_info.tsf >> 32)); + #set $num_header_words += 1 + packet_buff[$num_header_words] = $(XE_MACRO)(boost::uint32_t(if_packet_info.tsf >> 0)); + #set $num_header_words += 1 + #set $flags |= (0x1 << 20); + #end if + ########## Burst Flags ########## + #if $pred & $eob_p + #set $flags |= (0x1 << 24); + #end if + #if $pred & $sob_p + #set $flags |= (0x1 << 25); + #end if + ########## Trailer ########## + #if $pred & $tlr_p + { + const size_t empty_bytes = if_packet_info.num_payload_words32*sizeof(boost::uint32_t) - if_packet_info.num_payload_bytes; + if_packet_info.tlr = (0x3 << 22) | (occ_table[empty_bytes & 0x3] << 10); + } + packet_buff[$num_header_words+if_packet_info.num_payload_words32] = $(XE_MACRO)(if_packet_info.tlr); + #set $flags |= (0x1 << 26); + #set $num_trailer_words = 1; + #else + #set $num_trailer_words = 0; + #end if + ########## Variables ########## + if_packet_info.num_header_words32 = $num_header_words; + if_packet_info.num_packet_words32 = $($num_header_words + $num_trailer_words) + if_packet_info.num_payload_words32; + vrt_hdr_flags = $hex($flags); + break; + #end for + } + + //fill in complete header word + packet_buff[0] = $(XE_MACRO)(boost::uint32_t(0 + | (if_packet_info.packet_type << 29) + | vrt_hdr_flags + | ((if_packet_info.packet_count & 0xf) << 16) + | (if_packet_info.num_packet_words32 & 0xffff) + )); +} + +void vrt::if_hdr_unpack_$(suffix)( + const boost::uint32_t *packet_buff, + if_packet_info_t &if_packet_info +){ + //extract vrt header + boost::uint32_t vrt_hdr_word = $(XE_MACRO)(packet_buff[0]); + size_t packet_words32 = vrt_hdr_word & 0xffff; + + //failure case + if (if_packet_info.num_packet_words32 < packet_words32) + throw uhd::value_error("bad vrt header or packet fragment"); + + //extract fields from the header + if_packet_info.packet_type = if_packet_info_t::packet_type_t(vrt_hdr_word >> 29); + if_packet_info.packet_count = (vrt_hdr_word >> 16) & 0xf; + + const pred_type pred = pred_unpack_table[pred_table_index(vrt_hdr_word)]; + + size_t empty_bytes = 0; + + switch(pred){ + #for $pred in range(2**7) + case $pred: + #set $has_time_spec = False + #set $num_header_words = 1 + ########## Stream ID ########## + #if $pred & $sid_p + if_packet_info.has_sid = true; + if_packet_info.sid = $(XE_MACRO)(packet_buff[$num_header_words]); + #set $num_header_words += 1 + #else + if_packet_info.has_sid = false; + #end if + ########## Class ID ########## + #if $pred & $cid_p + if_packet_info.has_cid = true; + if_packet_info.cid = 0; //not implemented + #set $num_header_words += 2 + #else + if_packet_info.has_cid = false; + #end if + ########## Integer Time ########## + #if $pred & $tsi_p + if_packet_info.has_tsi = true; + if_packet_info.tsi = $(XE_MACRO)(packet_buff[$num_header_words]); + #set $num_header_words += 1 + #else + if_packet_info.has_tsi = false; + #end if + ########## Fractional Time ########## + #if $pred & $tsf_p + if_packet_info.has_tsf = true; + if_packet_info.tsf = boost::uint64_t($(XE_MACRO)(packet_buff[$num_header_words])) << 32; + #set $num_header_words += 1 + if_packet_info.tsf |= $(XE_MACRO)(packet_buff[$num_header_words]); + #set $num_header_words += 1 + #else + if_packet_info.has_tsf = false; + #end if + ########## Burst Flags ########## + #if $pred & $eob_p + if_packet_info.eob = true; + #else + if_packet_info.eob = false; + #end if + #if $pred & $sob_p + if_packet_info.sob = true; + #else + if_packet_info.sob = false; + #end if + ########## Trailer ########## + #if $pred & $tlr_p + if_packet_info.has_tlr = true; + if_packet_info.tlr = $(XE_MACRO)(packet_buff[packet_words32-1]); + #set $num_trailer_words = 1; + { + const int indicators = (if_packet_info.tlr >> 20) & (if_packet_info.tlr >> 8); + if ((indicators & (1 << 0)) != 0) if_packet_info.eob = true; + if ((indicators & (1 << 1)) != 0) if_packet_info.sob = true; + empty_bytes = occ_table[(indicators >> 2) & 0x3]; + } + #else + if_packet_info.has_tlr = false; + #set $num_trailer_words = 0; + #end if + ########## Variables ########## + //another failure case + if (packet_words32 < $($num_header_words + $num_trailer_words)) + throw uhd::value_error("bad vrt header or invalid packet length"); + if_packet_info.num_header_words32 = $num_header_words; + if_packet_info.num_payload_words32 = packet_words32 - $($num_header_words + $num_trailer_words); + if_packet_info.num_payload_bytes = if_packet_info.num_payload_words32*sizeof(boost::uint32_t) - empty_bytes; + break; + #end for + } +} + +######################################################################## +#end def +######################################################################## + +$gen_code("BE_MACRO", "be") +$gen_code("LE_MACRO", "le") +""" + +def parse_tmpl(_tmpl_text, **kwargs): + from Cheetah.Template import Template + return str(Template(_tmpl_text, kwargs)) + +if __name__ == '__main__': + import sys + open(sys.argv[1], 'w').write(parse_tmpl( + TMPL_TEXT, + file=__file__, + sid_p = 0b0000001, + cid_p = 0b0000010, + tsi_p = 0b0000100, + tsf_p = 0b0001000, + tlr_p = 0b0010000, + sob_p = 0b0100000, + eob_p = 0b1000000, + )) diff --git a/host/lib/transport/if_addrs.cpp b/host/lib/transport/if_addrs.cpp new file mode 100644 index 000000000..2ad0c8c53 --- /dev/null +++ b/host/lib/transport/if_addrs.cpp @@ -0,0 +1,121 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/if_addrs.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/cstdint.hpp> +#include <iostream> + +/*********************************************************************** + * Interface address discovery through ifaddrs api + **********************************************************************/ +#ifdef HAVE_GETIFADDRS +#include <ifaddrs.h> + +static boost::asio::ip::address_v4 sockaddr_to_ip_addr(sockaddr *addr){ + return boost::asio::ip::address_v4(ntohl( + reinterpret_cast<sockaddr_in*>(addr)->sin_addr.s_addr + )); +} + +std::vector<uhd::transport::if_addrs_t> uhd::transport::get_if_addrs(void){ + std::vector<if_addrs_t> if_addrs; + struct ifaddrs *ifap; + if (getifaddrs(&ifap) == 0){ + for (struct ifaddrs *iter = ifap; iter != NULL; iter = iter->ifa_next){ + //ensure that the entries are valid + if (iter->ifa_addr == NULL) continue; + if (iter->ifa_addr->sa_family != AF_INET) continue; + if (iter->ifa_netmask->sa_family != AF_INET) continue; + if (iter->ifa_broadaddr->sa_family != AF_INET) continue; + + //append a new set of interface addresses + if_addrs_t if_addr; + if_addr.inet = sockaddr_to_ip_addr(iter->ifa_addr).to_string(); + if_addr.mask = sockaddr_to_ip_addr(iter->ifa_netmask).to_string(); + if_addr.bcast = sockaddr_to_ip_addr(iter->ifa_broadaddr).to_string(); + + //correct the bcast address when its same as the gateway + if (if_addr.inet == if_addr.bcast or sockaddr_to_ip_addr(iter->ifa_broadaddr) == boost::asio::ip::address_v4(0)){ + //manually calculate broadcast address + //https://svn.boost.org/trac/boost/ticket/5198 + const boost::uint32_t addr = sockaddr_to_ip_addr(iter->ifa_addr).to_ulong(); + const boost::uint32_t mask = sockaddr_to_ip_addr(iter->ifa_netmask).to_ulong(); + const boost::uint32_t bcast = (addr & mask) | ~mask; + if_addr.bcast = boost::asio::ip::address_v4(bcast).to_string(); + } + + if_addrs.push_back(if_addr); + } + freeifaddrs(ifap); + } + return if_addrs; +} + +#endif /* HAVE_GETIFADDRS */ + +/*********************************************************************** + * Interface address discovery through windows api + **********************************************************************/ +#ifdef HAVE_SIO_GET_INTERFACE_LIST +#include <winsock2.h> + +std::vector<uhd::transport::if_addrs_t> uhd::transport::get_if_addrs(void){ + std::vector<if_addrs_t> if_addrs; + SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0); + if (sd == SOCKET_ERROR) { + std::cerr << "Failed to get a socket. Error " << WSAGetLastError() << + std::endl; return if_addrs; + } + + INTERFACE_INFO InterfaceList[20]; + unsigned long nBytesReturned; + if (WSAIoctl(sd, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList, + sizeof(InterfaceList), &nBytesReturned, 0, 0) == SOCKET_ERROR) { + std::cerr << "Failed calling WSAIoctl: error " << WSAGetLastError() << + std::endl; + return if_addrs; + } + + int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO); + for (int i = 0; i < nNumInterfaces; ++i) { + boost::uint32_t iiAddress = ntohl(reinterpret_cast<sockaddr_in&>(InterfaceList[i].iiAddress).sin_addr.s_addr); + boost::uint32_t iiNetmask = ntohl(reinterpret_cast<sockaddr_in&>(InterfaceList[i].iiNetmask).sin_addr.s_addr); + boost::uint32_t iiBroadcastAddress = (iiAddress & iiNetmask) | ~iiNetmask; + + if_addrs_t if_addr; + if_addr.inet = boost::asio::ip::address_v4(iiAddress).to_string(); + if_addr.mask = boost::asio::ip::address_v4(iiNetmask).to_string(); + if_addr.bcast = boost::asio::ip::address_v4(iiBroadcastAddress).to_string(); + if_addrs.push_back(if_addr); + } + + return if_addrs; +} + +#endif /* HAVE_SIO_GET_INTERFACE_LIST */ + +/*********************************************************************** + * Interface address discovery not included + **********************************************************************/ +#ifdef HAVE_IF_ADDRS_DUMMY + +std::vector<uhd::transport::if_addrs_t> uhd::transport::get_if_addrs(void){ + return std::vector<if_addrs_t>(); +} + +#endif /* HAVE_IF_ADDRS_DUMMY */ diff --git a/host/lib/transport/libusb1_base.cpp b/host/lib/transport/libusb1_base.cpp new file mode 100644 index 000000000..d4ec874f1 --- /dev/null +++ b/host/lib/transport/libusb1_base.cpp @@ -0,0 +1,279 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "libusb1_base.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/foreach.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::transport; + +/*********************************************************************** + * libusb session + **********************************************************************/ +class libusb_session_impl : public libusb::session{ +public: + libusb_session_impl(void){ + UHD_ASSERT_THROW(libusb_init(&_context) == 0); + libusb_set_debug(_context, debug_level); + } + + ~libusb_session_impl(void){ + libusb_exit(_context); + } + + libusb_context *get_context(void) const{ + return _context; + } + +private: + libusb_context *_context; +}; + +libusb::session::sptr libusb::session::get_global_session(void){ + static boost::weak_ptr<session> global_session; + + //not expired -> get existing session + if (not global_session.expired()) return global_session.lock(); + + //create a new global session + sptr new_global_session(new libusb_session_impl()); + global_session = new_global_session; + return new_global_session; +} + +/*********************************************************************** + * libusb device + **********************************************************************/ +class libusb_device_impl : public libusb::device{ +public: + libusb_device_impl(libusb_device *dev){ + _session = libusb::session::get_global_session(); + _dev = dev; + } + + ~libusb_device_impl(void){ + libusb_unref_device(this->get()); + } + + libusb_device *get(void) const{ + return _dev; + } + +private: + libusb::session::sptr _session; //always keep a reference to session + libusb_device *_dev; +}; + +/*********************************************************************** + * libusb device list + **********************************************************************/ +class libusb_device_list_impl : public libusb::device_list{ +public: + libusb_device_list_impl(void){ + libusb::session::sptr sess = libusb::session::get_global_session(); + + //allocate a new list of devices + libusb_device** dev_list; + ssize_t ret = libusb_get_device_list(sess->get_context(), &dev_list); + if (ret < 0) throw uhd::os_error("cannot enumerate usb devices"); + + //fill the vector of device references + for (size_t i = 0; i < size_t(ret); i++) _devs.push_back( + libusb::device::sptr(new libusb_device_impl(dev_list[i])) + ); + + //free the device list but dont unref (done in ~device) + libusb_free_device_list(dev_list, false/*dont unref*/); + } + + size_t size(void) const{ + return _devs.size(); + } + + libusb::device::sptr at(size_t i) const{ + return _devs.at(i); + } + +private: + std::vector<libusb::device::sptr> _devs; +}; + +libusb::device_list::sptr libusb::device_list::make(void){ + return sptr(new libusb_device_list_impl()); +} + +/*********************************************************************** + * libusb device descriptor + **********************************************************************/ +class libusb_device_descriptor_impl : public libusb::device_descriptor{ +public: + libusb_device_descriptor_impl(libusb::device::sptr dev){ + _dev = dev; + UHD_ASSERT_THROW(libusb_get_device_descriptor(_dev->get(), &_desc) == 0); + } + + const libusb_device_descriptor &get(void) const{ + return _desc; + } + + std::string get_ascii_serial(void) const{ + if (this->get().iSerialNumber == 0) return ""; + + libusb::device_handle::sptr handle( + libusb::device_handle::get_cached_handle(_dev) + ); + + unsigned char buff[512]; + ssize_t ret = libusb_get_string_descriptor_ascii( + handle->get(), this->get().iSerialNumber, buff, sizeof(buff) + ); + if (ret < 0) return ""; //on error, just return empty string + + return std::string((char *)buff, ret); + } + +private: + libusb::device::sptr _dev; //always keep a reference to device + libusb_device_descriptor _desc; +}; + +libusb::device_descriptor::sptr libusb::device_descriptor::make(device::sptr dev){ + return sptr(new libusb_device_descriptor_impl(dev)); +} + +/*********************************************************************** + * libusb device handle + **********************************************************************/ +class libusb_device_handle_impl : public libusb::device_handle{ +public: + libusb_device_handle_impl(libusb::device::sptr dev){ + _dev = dev; + UHD_ASSERT_THROW(libusb_open(_dev->get(), &_handle) == 0); + } + + ~libusb_device_handle_impl(void){ + //release all claimed interfaces + for (size_t i = 0; i < _claimed.size(); i++){ + libusb_release_interface(this->get(), _claimed[i]); + } + libusb_close(_handle); + } + + libusb_device_handle *get(void) const{ + return _handle; + } + + void claim_interface(int interface){ + UHD_ASSERT_THROW(libusb_claim_interface(this->get(), interface) == 0); + _claimed.push_back(interface); + } + +private: + libusb::device::sptr _dev; //always keep a reference to device + libusb_device_handle *_handle; + std::vector<int> _claimed; +}; + +libusb::device_handle::sptr libusb::device_handle::get_cached_handle(device::sptr dev){ + static uhd::dict<libusb_device *, boost::weak_ptr<device_handle> > handles; + + //lock for atomic access to static table above + static boost::mutex mutex; + boost::mutex::scoped_lock lock(mutex); + + //not expired -> get existing handle + if (handles.has_key(dev->get()) and not handles[dev->get()].expired()){ + return handles[dev->get()].lock(); + } + + //create a new cached handle + try{ + sptr new_handle(new libusb_device_handle_impl(dev)); + handles[dev->get()] = new_handle; + return new_handle; + } + catch(const uhd::exception &){ + #ifdef UHD_PLATFORM_LINUX + UHD_MSG(error) << + "USB open failed: insufficient permissions.\n" + "See the application notes for your device.\n" + << std::endl; + #else + UHD_LOG << "USB open failed: device already claimed." << std::endl; + #endif + throw; + } +} + +/*********************************************************************** + * libusb special handle + **********************************************************************/ +class libusb_special_handle_impl : public libusb::special_handle{ +public: + libusb_special_handle_impl(libusb::device::sptr dev){ + _dev = dev; + } + + libusb::device::sptr get_device(void) const{ + return _dev; + } + + std::string get_serial(void) const{ + return libusb::device_descriptor::make(this->get_device())->get_ascii_serial(); + } + + boost::uint16_t get_vendor_id(void) const{ + return libusb::device_descriptor::make(this->get_device())->get().idVendor; + } + + boost::uint16_t get_product_id(void) const{ + return libusb::device_descriptor::make(this->get_device())->get().idProduct; + } + +private: + libusb::device::sptr _dev; //always keep a reference to device +}; + +libusb::special_handle::sptr libusb::special_handle::make(device::sptr dev){ + return sptr(new libusb_special_handle_impl(dev)); +} + +/*********************************************************************** + * list device handles implementations + **********************************************************************/ +std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list( + boost::uint16_t vid, boost::uint16_t pid +){ + std::vector<usb_device_handle::sptr> handles; + + libusb::device_list::sptr dev_list = libusb::device_list::make(); + for (size_t i = 0; i < dev_list->size(); i++){ + usb_device_handle::sptr handle = libusb::special_handle::make(dev_list->at(i)); + if (handle->get_vendor_id() == vid and handle->get_product_id() == pid){ + handles.push_back(handle); + } + } + + return handles; +} diff --git a/host/lib/transport/libusb1_base.hpp b/host/lib/transport/libusb1_base.hpp new file mode 100644 index 000000000..04c1d6574 --- /dev/null +++ b/host/lib/transport/libusb1_base.hpp @@ -0,0 +1,149 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_LIBUSB_HPP +#define INCLUDED_LIBUHD_TRANSPORT_LIBUSB_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/transport/usb_device_handle.hpp> +#include <libusb.h> + +/*********************************************************************** + * Libusb object oriented smart pointer wrappers: + * The following wrappers provide allocation and automatic deallocation + * for various libusb data types and handles. The construction routines + * also store tables of already allocated structures to avoid multiple + * occurrences of opened handles (for example). + **********************************************************************/ +namespace uhd { namespace transport { + +namespace libusb { + + /*! + * This session class holds a global libusb context for this process. + * The get global session call will create a new context if none exists. + * When all references to session are destroyed, the context will be freed. + */ + class session : boost::noncopyable { + public: + typedef boost::shared_ptr<session> sptr; + + /*! + * Level 0: no messages ever printed by the library (default) + * Level 1: error messages are printed to stderr + * Level 2: warning and error messages are printed to stderr + * Level 3: informational messages are printed to stdout, warning + * and error messages are printed to stderr + */ + static const int debug_level = 0; + + //! get a shared pointer to the global session + static sptr get_global_session(void); + + //! get the underlying libusb context pointer + virtual libusb_context *get_context(void) const = 0; + }; + + /*! + * Holds a device pointer with a reference to the session. + */ + class device : boost::noncopyable { + public: + typedef boost::shared_ptr<device> sptr; + + //! get the underlying device pointer + virtual libusb_device *get(void) const = 0; + }; + + /*! + * This device list class holds a device list that will be + * automatically freed when the last reference is destroyed. + */ + class device_list : boost::noncopyable { + public: + typedef boost::shared_ptr<device_list> sptr; + + //! make a new device list + static sptr make(void); + + //! the number of devices in this list + virtual size_t size() const = 0; + + //! get the device pointer at a particular index + virtual device::sptr at(size_t index) const = 0; + }; + + /*! + * Holds a device descriptor and a reference to the device. + */ + class device_descriptor : boost::noncopyable { + public: + typedef boost::shared_ptr<device_descriptor> sptr; + + //! make a new descriptor from a device reference + static sptr make(device::sptr); + + //! get the underlying device descriptor + virtual const libusb_device_descriptor &get(void) const = 0; + + virtual std::string get_ascii_serial(void) const = 0; + }; + + /*! + * Holds a device handle and a reference to the device. + */ + class device_handle : boost::noncopyable { + public: + typedef boost::shared_ptr<device_handle> sptr; + + //! get a cached handle or make a new one given the device + static sptr get_cached_handle(device::sptr); + + //! get the underlying device handle + virtual libusb_device_handle *get(void) const = 0; + + /*! + * Open USB interfaces for control using magic value + * IN interface: 2 + * OUT interface: 1 + * Control interface: 0 + */ + virtual void claim_interface(int) = 0; + }; + + /*! + * The special handle is our internal implementation of the + * usb device handle which is used publicly to identify a device. + */ + class special_handle : public usb_device_handle { + public: + typedef boost::shared_ptr<special_handle> sptr; + + //! make a new special handle from device + static sptr make(device::sptr); + + //! get the underlying device reference + virtual device::sptr get_device(void) const = 0; + }; + +} + +}} //namespace + +#endif /* INCLUDED_LIBUHD_TRANSPORT_LIBUSB_HPP */ diff --git a/host/lib/transport/libusb1_control.cpp b/host/lib/transport/libusb1_control.cpp new file mode 100644 index 000000000..3d9b38785 --- /dev/null +++ b/host/lib/transport/libusb1_control.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "libusb1_base.hpp" +#include <uhd/transport/usb_control.hpp> +#include <boost/thread/mutex.hpp> + +using namespace uhd::transport; + +const int libusb_timeout = 0; + +/*********************************************************************** + * libusb-1.0 implementation of USB control transport + **********************************************************************/ +class libusb_control_impl : public usb_control { +public: + libusb_control_impl(libusb::device_handle::sptr handle, const size_t interface): + _handle(handle) + { + _handle->claim_interface(interface); + } + + ssize_t submit(boost::uint8_t request_type, + boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length + ){ + boost::mutex::scoped_lock lock(_mutex); + return libusb_control_transfer(_handle->get(), + request_type, + request, + value, + index, + buff, + length, + libusb_timeout); + } + +private: + libusb::device_handle::sptr _handle; + boost::mutex _mutex; +}; + +/*********************************************************************** + * USB control public make functions + **********************************************************************/ +usb_control::sptr usb_control::make(usb_device_handle::sptr handle, const size_t interface){ + return sptr(new libusb_control_impl(libusb::device_handle::get_cached_handle( + boost::static_pointer_cast<libusb::special_handle>(handle)->get_device() + ), interface)); +} diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp new file mode 100644 index 000000000..3e67264cd --- /dev/null +++ b/host/lib/transport/libusb1_zero_copy.cpp @@ -0,0 +1,300 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "libusb1_base.hpp" +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/thread.hpp> +#include <list> + +using namespace uhd; +using namespace uhd::transport; + +static const size_t DEFAULT_NUM_XFERS = 16; //num xfers +static const size_t DEFAULT_XFER_SIZE = 32*512; //bytes + +//! Define LIBUSB_CALL when its missing (non-windows) +#ifndef LIBUSB_CALL + #define LIBUSB_CALL +#endif /*LIBUSB_CALL*/ + +/*! + * All libusb callback functions should be marked with the LIBUSB_CALL macro + * to ensure that they are compiled with the same calling convention as libusb. + */ + +//! helper function: handles all async callbacks +static void LIBUSB_CALL libusb_async_cb(libusb_transfer *lut){ + *(static_cast<bool *>(lut->user_data)) = true; +} + +/*! + * Wait for a managed buffer to become complete. + * + * This routine processes async events until the transaction completes. + * We must call the libusb handle events in a loop because the handler + * may complete managed buffers other than the one we are waiting on. + * + * We cannot determine if handle events timed out or processed an event. + * Therefore, the timeout condition is handled by using boost system time. + * + * \param ctx the libusb context structure + * \param timeout the wait timeout in seconds + * \param completed a reference to the completed flag + * \return true for completion, false for timeout + */ +UHD_INLINE bool wait_for_completion(libusb_context *ctx, const double timeout, bool &completed){ + const boost::system_time timeout_time = boost::get_system_time() + boost::posix_time::microseconds(long(timeout*1000000)); + + while (not completed and (boost::get_system_time() < timeout_time)){ + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 10000; /*10ms*/ + libusb_handle_events_timeout(ctx, &tv); + } + + return completed; +} + +/*********************************************************************** + * Reusable managed receiver buffer: + * - Associated with a particular libusb transfer struct. + * - Submits the transfer to libusb in the release method. + **********************************************************************/ +class libusb_zero_copy_mrb : public managed_recv_buffer{ +public: + libusb_zero_copy_mrb(libusb_transfer *lut, const size_t frame_size): + _ctx(libusb::session::get_global_session()->get_context()), + _lut(lut), _expired(false), _frame_size(frame_size) { /* NOP */ } + + void release(void){ + if (_expired) return; + completed = false; + _lut->length = _frame_size; //always reset length + UHD_ASSERT_THROW(libusb_submit_transfer(_lut) == 0); + _expired = true; + } + + sptr get_new(const double timeout, size_t &index){ + if (wait_for_completion(_ctx, timeout, completed)){ + index++; + _expired = false; + return make_managed_buffer(this); + } + return managed_recv_buffer::sptr(); + } + + bool completed; + +private: + const void *get_buff(void) const{return _lut->buffer;} + size_t get_size(void) const{return _lut->actual_length;} + + libusb_context *_ctx; + libusb_transfer *_lut; + bool _expired; + const size_t _frame_size; +}; + +/*********************************************************************** + * Reusable managed send buffer: + * - Associated with a particular libusb transfer struct. + * - Submits the transfer to libusb in the commit method. + **********************************************************************/ +class libusb_zero_copy_msb : public managed_send_buffer{ +public: + libusb_zero_copy_msb(libusb_transfer *lut, const size_t frame_size): + _ctx(libusb::session::get_global_session()->get_context()), + _lut(lut), _expired(false), _frame_size(frame_size) { /* NOP */ } + + void commit(size_t len){ + if (_expired) return; + completed = false; + _lut->length = len; + if (len == 0) libusb_async_cb(_lut); + else UHD_ASSERT_THROW(libusb_submit_transfer(_lut) == 0); + _expired = true; + } + + sptr get_new(const double timeout, size_t &index){ + if (wait_for_completion(_ctx, timeout, completed)){ + index++; + _expired = false; + return make_managed_buffer(this); + } + return managed_send_buffer::sptr(); + } + + bool completed; + +private: + void *get_buff(void) const{return _lut->buffer;} + size_t get_size(void) const{return _frame_size;} + + libusb_context *_ctx; + libusb_transfer *_lut; + bool _expired; + const size_t _frame_size; +}; + +/*********************************************************************** + * USB zero_copy device class + **********************************************************************/ +class libusb_zero_copy_impl : public usb_zero_copy{ +public: + + libusb_zero_copy_impl( + libusb::device_handle::sptr handle, + const size_t recv_interface, + const size_t recv_endpoint, + const size_t send_interface, + const size_t send_endpoint, + const device_addr_t &hints + ): + _handle(handle), + _recv_frame_size(size_t(hints.cast<double>("recv_frame_size", DEFAULT_XFER_SIZE))), + _num_recv_frames(size_t(hints.cast<double>("num_recv_frames", DEFAULT_NUM_XFERS))), + _send_frame_size(size_t(hints.cast<double>("send_frame_size", DEFAULT_XFER_SIZE))), + _num_send_frames(size_t(hints.cast<double>("num_send_frames", DEFAULT_NUM_XFERS))), + _recv_buffer_pool(buffer_pool::make(_num_recv_frames, _recv_frame_size)), + _send_buffer_pool(buffer_pool::make(_num_send_frames, _send_frame_size)), + _next_recv_buff_index(0), + _next_send_buff_index(0) + { + _handle->claim_interface(recv_interface); + _handle->claim_interface(send_interface); + + //allocate libusb transfer structs and managed receive buffers + for (size_t i = 0; i < get_num_recv_frames(); i++){ + + libusb_transfer *lut = libusb_alloc_transfer(0); + UHD_ASSERT_THROW(lut != NULL); + + _mrb_pool.push_back(boost::shared_ptr<libusb_zero_copy_mrb>(new libusb_zero_copy_mrb(lut, this->get_recv_frame_size()))); + + libusb_fill_bulk_transfer( + lut, // transfer + _handle->get(), // dev_handle + (recv_endpoint & 0x7f) | 0x80, // endpoint + static_cast<unsigned char *>(_recv_buffer_pool->at(i)), // buffer + this->get_recv_frame_size(), // length + libusb_transfer_cb_fn(&libusb_async_cb), // callback + static_cast<void *>(&_mrb_pool.back()->completed), // user_data + 0 // timeout (ms) + ); + + _all_luts.push_back(lut); + _mrb_pool.back()->release(); + } + + //allocate libusb transfer structs and managed send buffers + for (size_t i = 0; i < get_num_send_frames(); i++){ + + libusb_transfer *lut = libusb_alloc_transfer(0); + UHD_ASSERT_THROW(lut != NULL); + + _msb_pool.push_back(boost::shared_ptr<libusb_zero_copy_msb>(new libusb_zero_copy_msb(lut, this->get_send_frame_size()))); + + libusb_fill_bulk_transfer( + lut, // transfer + _handle->get(), // dev_handle + (send_endpoint & 0x7f) | 0x00, // endpoint + static_cast<unsigned char *>(_send_buffer_pool->at(i)), // buffer + this->get_send_frame_size(), // length + libusb_transfer_cb_fn(&libusb_async_cb), // callback + static_cast<void *>(&_msb_pool.back()->completed), // user_data + 0 // timeout + ); + + _all_luts.push_back(lut); + _msb_pool.back()->commit(0); + } + } + + ~libusb_zero_copy_impl(void){ + libusb_context *ctx = libusb::session::get_global_session()->get_context(); + + //cancel all transfers + BOOST_FOREACH(libusb_transfer *lut, _all_luts){ + libusb_cancel_transfer(lut); + } + + //process all transfers until timeout occurs + bool completed = false; + wait_for_completion(ctx, 0.01, completed); + + //free all transfers + BOOST_FOREACH(libusb_transfer *lut, _all_luts){ + libusb_free_transfer(lut); + } + + } + + managed_recv_buffer::sptr get_recv_buff(double timeout){ + if (_next_recv_buff_index == _num_recv_frames) _next_recv_buff_index = 0; + return _mrb_pool[_next_recv_buff_index]->get_new(timeout, _next_recv_buff_index); + } + + managed_send_buffer::sptr get_send_buff(double timeout){ + if (_next_send_buff_index == _num_send_frames) _next_send_buff_index = 0; + return _msb_pool[_next_send_buff_index]->get_new(timeout, _next_send_buff_index); + } + + size_t get_num_recv_frames(void) const { return _num_recv_frames; } + size_t get_num_send_frames(void) const { return _num_send_frames; } + + size_t get_recv_frame_size(void) const { return _recv_frame_size; } + size_t get_send_frame_size(void) const { return _send_frame_size; } + +private: + libusb::device_handle::sptr _handle; + const size_t _recv_frame_size, _num_recv_frames; + const size_t _send_frame_size, _num_send_frames; + + //! Storage for transfer related objects + buffer_pool::sptr _recv_buffer_pool, _send_buffer_pool; + std::vector<boost::shared_ptr<libusb_zero_copy_mrb> > _mrb_pool; + std::vector<boost::shared_ptr<libusb_zero_copy_msb> > _msb_pool; + size_t _next_recv_buff_index, _next_send_buff_index; + + //! a list of all transfer structs we allocated + std::list<libusb_transfer *> _all_luts; + + +}; + +/*********************************************************************** + * USB zero_copy make functions + **********************************************************************/ +usb_zero_copy::sptr usb_zero_copy::make( + usb_device_handle::sptr handle, + const size_t recv_interface, + const size_t recv_endpoint, + const size_t send_interface, + const size_t send_endpoint, + const device_addr_t &hints +){ + libusb::device_handle::sptr dev_handle(libusb::device_handle::get_cached_handle( + boost::static_pointer_cast<libusb::special_handle>(handle)->get_device() + )); + return sptr(new libusb_zero_copy_impl( + dev_handle, recv_interface, recv_endpoint, send_interface, send_endpoint, hints + )); +} diff --git a/host/lib/transport/super_recv_packet_handler.hpp b/host/lib/transport/super_recv_packet_handler.hpp new file mode 100644 index 000000000..74fbe82fb --- /dev/null +++ b/host/lib/transport/super_recv_packet_handler.hpp @@ -0,0 +1,582 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_SUPER_RECV_PACKET_HANDLER_HPP +#define INCLUDED_LIBUHD_TRANSPORT_SUPER_RECV_PACKET_HANDLER_HPP + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/convert.hpp> +#include <uhd/stream.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/dynamic_bitset.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <vector> + +namespace uhd{ namespace transport{ namespace sph{ + +UHD_INLINE boost::uint32_t get_context_code( + const boost::uint32_t *vrt_hdr, const vrt::if_packet_info_t &if_packet_info +){ + //extract the context word (we dont know the endianness so mirror the bytes) + boost::uint32_t word0 = vrt_hdr[if_packet_info.num_header_words32] | + uhd::byteswap(vrt_hdr[if_packet_info.num_header_words32]); + return word0 & 0xff; +} + +typedef boost::function<void(void)> handle_overflow_type; +static inline void handle_overflow_nop(void){} + +/*********************************************************************** + * Super receive packet handler + * + * A receive packet handler represents a group of channels. + * The channel group shares a common sample rate. + * All channels are received in unison in recv(). + **********************************************************************/ +class recv_packet_handler{ +public: + typedef boost::function<managed_recv_buffer::sptr(double)> get_buff_type; + typedef void(*vrt_unpacker_type)(const boost::uint32_t *, vrt::if_packet_info_t &); + //typedef boost::function<void(const boost::uint32_t *, vrt::if_packet_info_t &)> vrt_unpacker_type; + + /*! + * Make a new packet handler for receive + * \param size the number of transport channels + */ + recv_packet_handler(const size_t size = 1): + _queue_error_for_next_call(false), + _buffers_infos_index(0) + { + this->resize(size); + set_alignment_failure_threshold(1000); + } + + //! Resize the number of transport channels + void resize(const size_t size){ + if (this->size() == size) return; + _props.resize(size); + //re-initialize all buffers infos by re-creating the vector + _buffers_infos = std::vector<buffers_info_type>(4, buffers_info_type(size)); + } + + //! Get the channel width of this handler + size_t size(void) const{ + return _props.size(); + } + + //! Setup the vrt unpacker function and offset + void set_vrt_unpacker(const vrt_unpacker_type &vrt_unpacker, const size_t header_offset_words32 = 0){ + _vrt_unpacker = vrt_unpacker; + _header_offset_words32 = header_offset_words32; + } + + /*! + * Set the threshold for alignment failure. + * How many packets throw out before giving up? + * \param threshold number of packets per channel + */ + void set_alignment_failure_threshold(const size_t threshold){ + _alignment_faulure_threshold = threshold*this->size(); + } + + //! Set the rate of ticks per second + void set_tick_rate(const double rate){ + _tick_rate = rate; + } + + //! Set the rate of samples per second + void set_samp_rate(const double rate){ + _samp_rate = rate; + } + + /*! + * Set the function to get a managed buffer. + * \param xport_chan which transport channel + * \param get_buff the getter function + */ + void set_xport_chan_get_buff(const size_t xport_chan, const get_buff_type &get_buff, const bool flush = false){ + if (flush){ + while (get_buff(0.0)); + } + _props.at(xport_chan).get_buff = get_buff; + } + + //! Set the conversion routine for all channels + void set_converter(const uhd::convert::id_type &id){ + _io_buffs.resize(id.num_outputs); + _converter = uhd::convert::get_converter(id)(); + this->set_scale_factor(1/32767.); //update after setting converter + _bytes_per_otw_item = uhd::convert::get_bytes_per_item(id.input_format); + _bytes_per_cpu_item = uhd::convert::get_bytes_per_item(id.output_format); + } + + //! Set the transport channel's overflow handler + void set_overflow_handler(const size_t xport_chan, const handle_overflow_type &handle_overflow){ + _props.at(xport_chan).handle_overflow = handle_overflow; + } + + //! Set the scale factor used in float conversion + void set_scale_factor(const double scale_factor){ + _converter->set_scalar(scale_factor); + } + + /******************************************************************* + * Receive: + * The entry point for the fast-path receive calls. + * Dispatch into combinations of single packet receive calls. + ******************************************************************/ + UHD_INLINE size_t recv( + const uhd::rx_streamer::buffs_type &buffs, + const size_t nsamps_per_buff, + uhd::rx_metadata_t &metadata, + const double timeout, + const bool one_packet + ){ + //handle metadata queued from a previous receive + if (_queue_error_for_next_call){ + _queue_error_for_next_call = false; + metadata = _queue_metadata; + //We want to allow a full buffer recv to be cut short by a timeout, + //but do not want to generate an inline timeout message packet. + if (_queue_metadata.error_code != rx_metadata_t::ERROR_CODE_TIMEOUT) return 0; + } + + size_t accum_num_samps = recv_one_packet( + buffs, nsamps_per_buff, metadata, timeout + ); + + if (one_packet) return accum_num_samps; + + //first recv had an error code set, return immediately + if (metadata.error_code != rx_metadata_t::ERROR_CODE_NONE) return accum_num_samps; + + //loop until buffer is filled or error code + while(accum_num_samps < nsamps_per_buff){ + size_t num_samps = recv_one_packet( + buffs, nsamps_per_buff - accum_num_samps, _queue_metadata, + timeout, accum_num_samps*_bytes_per_cpu_item + ); + + //metadata had an error code set, store for next call and return + if (_queue_metadata.error_code != rx_metadata_t::ERROR_CODE_NONE){ + _queue_error_for_next_call = true; + break; + } + accum_num_samps += num_samps; + } + return accum_num_samps; + } + +private: + + vrt_unpacker_type _vrt_unpacker; + size_t _header_offset_words32; + double _tick_rate, _samp_rate; + bool _queue_error_for_next_call; + size_t _alignment_faulure_threshold; + rx_metadata_t _queue_metadata; + struct xport_chan_props_type{ + xport_chan_props_type(void): + packet_count(0), + handle_overflow(&handle_overflow_nop) + {} + get_buff_type get_buff; + size_t packet_count; + handle_overflow_type handle_overflow; + }; + std::vector<xport_chan_props_type> _props; + std::vector<void *> _io_buffs; //used in conversion + size_t _bytes_per_otw_item; //used in conversion + size_t _bytes_per_cpu_item; //used in conversion + uhd::convert::converter::sptr _converter; //used in conversion + + //! information stored for a received buffer + struct per_buffer_info_type{ + managed_recv_buffer::sptr buff; + const boost::uint32_t *vrt_hdr; + vrt::if_packet_info_t ifpi; + time_spec_t time; + const char *copy_buff; + }; + + //!information stored for a set of aligned buffers + struct buffers_info_type : std::vector<per_buffer_info_type> { + buffers_info_type(const size_t size): + std::vector<per_buffer_info_type>(size), + indexes_todo(size, true), + alignment_time_valid(false), + data_bytes_to_copy(0), + fragment_offset_in_samps(0) + {/* NOP */} + boost::dynamic_bitset<> indexes_todo; //used in alignment logic + time_spec_t alignment_time; //used in alignment logic + bool alignment_time_valid; //used in alignment logic + size_t data_bytes_to_copy; //keeps track of state + size_t fragment_offset_in_samps; //keeps track of state + rx_metadata_t metadata; //packet description + }; + + //! a circular queue of buffer infos + std::vector<buffers_info_type> _buffers_infos; + size_t _buffers_infos_index; + buffers_info_type &get_curr_buffer_info(void){return _buffers_infos[_buffers_infos_index];} + buffers_info_type &get_prev_buffer_info(void){return _buffers_infos[(_buffers_infos_index + 3)%4];} + buffers_info_type &get_next_buffer_info(void){return _buffers_infos[(_buffers_infos_index + 1)%4];} + void increment_buffer_info(void){_buffers_infos_index = (_buffers_infos_index + 1)%4;} + + //! possible return options for the packet receiver + enum packet_type{ + PACKET_IF_DATA, + PACKET_TIMESTAMP_ERROR, + PACKET_INLINE_MESSAGE, + PACKET_TIMEOUT_ERROR, + PACKET_SEQUENCE_ERROR + }; + + /******************************************************************* + * Get and process a single packet from the transport: + * Receive a single packet at the given index. + * Extract all the relevant info and store. + * Check the info to determine the return code. + ******************************************************************/ + UHD_INLINE packet_type get_and_process_single_packet( + const size_t index, + buffers_info_type &prev_buffer_info, + buffers_info_type &curr_buffer_info, + double timeout + ){ + //get a single packet from the transport layer + managed_recv_buffer::sptr &buff = curr_buffer_info[index].buff; + buff = _props[index].get_buff(timeout); + if (buff.get() == NULL) return PACKET_TIMEOUT_ERROR; + + //bounds check before extract + size_t num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + if (num_packet_words32 <= _header_offset_words32){ + throw std::runtime_error("recv buffer smaller than vrt packet offset"); + } + + //extract packet info + per_buffer_info_type &info = curr_buffer_info[index]; + info.ifpi.num_packet_words32 = num_packet_words32 - _header_offset_words32; + info.vrt_hdr = buff->cast<const boost::uint32_t *>() + _header_offset_words32; + _vrt_unpacker(info.vrt_hdr, info.ifpi); + info.time = time_spec_t::from_ticks(info.ifpi.tsf, _tick_rate); //assumes has_tsf is true + info.copy_buff = reinterpret_cast<const char *>(info.vrt_hdr + info.ifpi.num_header_words32); + + //-------------------------------------------------------------- + //-- Determine return conditions: + //-- The order of these checks is HOLY. + //-------------------------------------------------------------- + + //1) check for inline IF message packets + if (info.ifpi.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + return PACKET_INLINE_MESSAGE; + } + + //2) check for sequence errors + #ifndef SRPH_DONT_CHECK_SEQUENCE + const size_t expected_packet_count = _props[index].packet_count; + _props[index].packet_count = (info.ifpi.packet_count + 1)%16; + if (expected_packet_count != info.ifpi.packet_count){ + return PACKET_SEQUENCE_ERROR; + } + #endif + + //3) check for out of order timestamps + if (info.ifpi.has_tsi and info.ifpi.has_tsf and prev_buffer_info[index].time > info.time){ + return PACKET_TIMESTAMP_ERROR; + } + + //4) otherwise the packet is normal! + return PACKET_IF_DATA; + } + + /******************************************************************* + * Alignment check: + * Check the received packet for alignment and mark accordingly. + ******************************************************************/ + UHD_INLINE void alignment_check( + const size_t index, buffers_info_type &info + ){ + //if alignment time was not valid or if the sequence id is newer: + // use this index's time as the alignment time + // reset the indexes list and remove this index + if (not info.alignment_time_valid or info[index].time > info.alignment_time){ + info.alignment_time_valid = true; + info.alignment_time = info[index].time; + info.indexes_todo.set(); + info.indexes_todo.reset(index); + info.data_bytes_to_copy = info[index].ifpi.num_payload_bytes; + } + + //if the sequence id matches: + // remove this index from the list and continue + else if (info[index].time == info.alignment_time){ + info.indexes_todo.reset(index); + } + + //if the sequence id is older: + // continue with the same index to try again + //else if (info[index].time < info.alignment_time)... + } + + /******************************************************************* + * Get aligned buffers: + * Iterate through each index and try to accumulate aligned buffers. + * Handle all of the edge cases like inline messages and errors. + * The logic will throw out older packets until it finds a match. + ******************************************************************/ + UHD_INLINE void get_aligned_buffs(double timeout){ + + increment_buffer_info(); //increment to next buffer + buffers_info_type &prev_info = get_prev_buffer_info(); + buffers_info_type &curr_info = get_curr_buffer_info(); + buffers_info_type &next_info = get_next_buffer_info(); + + //Loop until we get a message of an aligned set of buffers: + // - Receive a single packet and extract its info. + // - Handle the packet type yielded by the receive. + // - Check the timestamps for alignment conditions. + size_t iterations = 0; + while (curr_info.indexes_todo.any()){ + + //get the index to process for this iteration + const size_t index = curr_info.indexes_todo.find_first(); + packet_type packet; + + //receive a single packet from the transport + try{ + packet = get_and_process_single_packet( + index, prev_info, curr_info, timeout + ); + } + + //handle the case when the get packet throws + catch(const std::exception &e){ + UHD_MSG(error) << boost::format( + "The receive packet handler caught an exception.\n%s" + ) % e.what() << std::endl; + std::swap(curr_info, next_info); //save progress from curr -> next + curr_info.metadata.has_time_spec = false; + curr_info.metadata.time_spec = time_spec_t(0.0); + curr_info.metadata.more_fragments = false; + curr_info.metadata.fragment_offset = 0; + curr_info.metadata.start_of_burst = false; + curr_info.metadata.end_of_burst = false; + curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_BAD_PACKET; + return; + } + + switch(packet){ + case PACKET_IF_DATA: + alignment_check(index, curr_info); + break; + + case PACKET_TIMESTAMP_ERROR: + //If the user changes the device time while streaming or without flushing, + //we can receive a packet that comes before the previous packet in time. + //This could cause the alignment logic to discard future received packets. + //Therefore, when this occurs, we reset the info to restart from scratch. + if (curr_info.alignment_time_valid and curr_info.alignment_time != curr_info[index].time){ + curr_info.alignment_time_valid = false; + } + alignment_check(index, curr_info); + break; + + case PACKET_INLINE_MESSAGE: + std::swap(curr_info, next_info); //save progress from curr -> next + curr_info.metadata.has_time_spec = next_info[index].ifpi.has_tsf; + curr_info.metadata.time_spec = next_info[index].time; + curr_info.metadata.more_fragments = false; + curr_info.metadata.fragment_offset = 0; + curr_info.metadata.start_of_burst = false; + curr_info.metadata.end_of_burst = false; + curr_info.metadata.error_code = rx_metadata_t::error_code_t(get_context_code(next_info[index].vrt_hdr, next_info[index].ifpi)); + if (curr_info.metadata.error_code == rx_metadata_t::ERROR_CODE_OVERFLOW){ + _props[index].handle_overflow(); + UHD_MSG(fastpath) << "O"; + } + return; + + case PACKET_TIMEOUT_ERROR: + std::swap(curr_info, next_info); //save progress from curr -> next + curr_info.metadata.has_time_spec = false; + curr_info.metadata.time_spec = time_spec_t(0.0); + curr_info.metadata.more_fragments = false; + curr_info.metadata.fragment_offset = 0; + curr_info.metadata.start_of_burst = false; + curr_info.metadata.end_of_burst = false; + curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_TIMEOUT; + return; + + case PACKET_SEQUENCE_ERROR: + alignment_check(index, curr_info); + std::swap(curr_info, next_info); //save progress from curr -> next + curr_info.metadata.has_time_spec = prev_info.metadata.has_time_spec; + curr_info.metadata.time_spec = prev_info.metadata.time_spec + time_spec_t::from_ticks( + prev_info[index].ifpi.num_payload_words32*sizeof(boost::uint32_t)/_bytes_per_otw_item, _samp_rate); + curr_info.metadata.more_fragments = false; + curr_info.metadata.fragment_offset = 0; + curr_info.metadata.start_of_burst = false; + curr_info.metadata.end_of_burst = false; + curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_OVERFLOW; + UHD_MSG(fastpath) << "O"; + return; + + } + + //too many iterations: detect alignment failure + if (iterations++ > _alignment_faulure_threshold){ + UHD_MSG(error) << boost::format( + "The receive packet handler failed to time-align packets.\n" + "%u received packets were processed by the handler.\n" + "However, a timestamp match could not be determined.\n" + ) % iterations << std::endl; + std::swap(curr_info, next_info); //save progress from curr -> next + curr_info.metadata.has_time_spec = false; + curr_info.metadata.time_spec = time_spec_t(0.0); + curr_info.metadata.more_fragments = false; + curr_info.metadata.fragment_offset = 0; + curr_info.metadata.start_of_burst = false; + curr_info.metadata.end_of_burst = false; + curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_ALIGNMENT; + return; + } + + } + + //set the metadata from the buffer information at index zero + curr_info.metadata.has_time_spec = curr_info[0].ifpi.has_tsf; + curr_info.metadata.time_spec = curr_info[0].time; + curr_info.metadata.more_fragments = false; + curr_info.metadata.fragment_offset = 0; + curr_info.metadata.start_of_burst = curr_info[0].ifpi.sob; + curr_info.metadata.end_of_burst = curr_info[0].ifpi.eob; + curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_NONE; + + } + + /******************************************************************* + * Receive a single packet: + * Handles fragmentation, messages, errors, and copy-conversion. + * When no fragments are available, call the get aligned buffers. + * Then copy-convert available data into the user's IO buffers. + ******************************************************************/ + UHD_INLINE size_t recv_one_packet( + const uhd::rx_streamer::buffs_type &buffs, + const size_t nsamps_per_buff, + uhd::rx_metadata_t &metadata, + const double timeout, + const size_t buffer_offset_bytes = 0 + ){ + //get the next buffer if the current one has expired + if (get_curr_buffer_info().data_bytes_to_copy == 0){ + + //reset current buffer info members for reuse + get_curr_buffer_info().fragment_offset_in_samps = 0; + get_curr_buffer_info().alignment_time_valid = false; + get_curr_buffer_info().indexes_todo.set(); + + //perform receive with alignment logic + get_aligned_buffs(timeout); + } + + buffers_info_type &info = get_curr_buffer_info(); + metadata = info.metadata; + + //interpolate the time spec (useful when this is a fragment) + metadata.time_spec += time_spec_t::from_ticks(info.fragment_offset_in_samps, _samp_rate); + + //extract the number of samples available to copy + const size_t nsamps_available = info.data_bytes_to_copy/_bytes_per_otw_item; + const size_t nsamps_to_copy = std::min(nsamps_per_buff*_io_buffs.size(), nsamps_available); + const size_t bytes_to_copy = nsamps_to_copy*_bytes_per_otw_item; + const size_t nsamps_to_copy_per_io_buff = nsamps_to_copy/_io_buffs.size(); + + size_t buff_index = 0; + BOOST_FOREACH(per_buffer_info_type &buff_info, info){ + + //fill a vector with pointers to the io buffers + BOOST_FOREACH(void *&io_buff, _io_buffs){ + io_buff = reinterpret_cast<char *>(buffs[buff_index++]) + buffer_offset_bytes; + } + + //copy-convert the samples from the recv buffer + _converter->conv(buff_info.copy_buff, _io_buffs, nsamps_to_copy_per_io_buff); + + //update the rx copy buffer to reflect the bytes copied + buff_info.copy_buff += bytes_to_copy; + } + //update the copy buffer's availability + info.data_bytes_to_copy -= bytes_to_copy; + + //setup the fragment flags and offset + metadata.more_fragments = info.data_bytes_to_copy != 0; + metadata.fragment_offset = info.fragment_offset_in_samps; + info.fragment_offset_in_samps += nsamps_to_copy; //set for next call + + //done with buffers? this action releases buffers in-order + if (not metadata.more_fragments){ + BOOST_FOREACH(per_buffer_info_type &buff_info, info){ + buff_info.buff.reset(); //effectively a release + } + } + + return nsamps_to_copy_per_io_buff; + } +}; + +class recv_packet_streamer : public recv_packet_handler, public rx_streamer{ +public: + recv_packet_streamer(const size_t max_num_samps){ + _max_num_samps = max_num_samps; + } + + size_t get_num_channels(void) const{ + return this->size(); + } + + size_t get_max_num_samps(void) const{ + return _max_num_samps; + } + + size_t recv( + const rx_streamer::buffs_type &buffs, + const size_t nsamps_per_buff, + uhd::rx_metadata_t &metadata, + const double timeout, + const bool one_packet + ){ + return recv_packet_handler::recv(buffs, nsamps_per_buff, metadata, timeout, one_packet); + } + +private: + size_t _max_num_samps; +}; + +}}} //namespace + +#endif /* INCLUDED_LIBUHD_TRANSPORT_SUPER_RECV_PACKET_HANDLER_HPP */ diff --git a/host/lib/transport/super_send_packet_handler.hpp b/host/lib/transport/super_send_packet_handler.hpp new file mode 100644 index 000000000..46c98afea --- /dev/null +++ b/host/lib/transport/super_send_packet_handler.hpp @@ -0,0 +1,282 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_SUPER_SEND_PACKET_HANDLER_HPP +#define INCLUDED_LIBUHD_TRANSPORT_SUPER_SEND_PACKET_HANDLER_HPP + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/convert.hpp> +#include <uhd/stream.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/thread/thread_time.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <iostream> +#include <vector> + +namespace uhd{ namespace transport{ namespace sph{ + +/*********************************************************************** + * Super send packet handler + * + * A send packet handler represents a group of channels. + * The channel group shares a common sample rate. + * All channels are sent in unison in send(). + **********************************************************************/ +class send_packet_handler{ +public: + typedef boost::function<managed_send_buffer::sptr(double)> get_buff_type; + typedef void(*vrt_packer_type)(boost::uint32_t *, vrt::if_packet_info_t &); + //typedef boost::function<void(boost::uint32_t *, vrt::if_packet_info_t &)> vrt_packer_type; + + /*! + * Make a new packet handler for send + * \param size the number of transport channels + */ + send_packet_handler(const size_t size = 1): + _next_packet_seq(0) + { + this->resize(size); + } + + //! Resize the number of transport channels + void resize(const size_t size){ + if (this->size() == size) return; + _props.resize(size); + static const boost::uint64_t zero = 0; + _zero_buffs.resize(size, &zero); + } + + //! Get the channel width of this handler + size_t size(void) const{ + return _props.size(); + } + + //! Setup the vrt packer function and offset + void set_vrt_packer(const vrt_packer_type &vrt_packer, const size_t header_offset_words32 = 0){ + _vrt_packer = vrt_packer; + _header_offset_words32 = header_offset_words32; + } + + //! Set the rate of ticks per second + void set_tick_rate(const double rate){ + _tick_rate = rate; + } + + //! Set the rate of samples per second + void set_samp_rate(const double rate){ + _samp_rate = rate; + } + + /*! + * Set the function to get a managed buffer. + * \param xport_chan which transport channel + * \param get_buff the getter function + */ + void set_xport_chan_get_buff(const size_t xport_chan, const get_buff_type &get_buff){ + _props.at(xport_chan).get_buff = get_buff; + } + + //! Set the conversion routine for all channels + void set_converter(const uhd::convert::id_type &id){ + _io_buffs.resize(id.num_inputs); + _converter = uhd::convert::get_converter(id)(); + this->set_scale_factor(32767.); //update after setting converter + _bytes_per_otw_item = uhd::convert::get_bytes_per_item(id.output_format); + _bytes_per_cpu_item = uhd::convert::get_bytes_per_item(id.input_format); + } + + /*! + * Set the maximum number of samples per host packet. + * Ex: A USRP1 in dual channel mode would be half. + * \param num_samps the maximum samples in a packet + */ + void set_max_samples_per_packet(const size_t num_samps){ + _max_samples_per_packet = num_samps; + } + + //! Set the scale factor used in float conversion + void set_scale_factor(const double scale_factor){ + _converter->set_scalar(scale_factor); + } + + /******************************************************************* + * Send: + * The entry point for the fast-path send calls. + * Dispatch into combinations of single packet send calls. + ******************************************************************/ + UHD_INLINE size_t send( + const uhd::tx_streamer::buffs_type &buffs, + const size_t nsamps_per_buff, + const uhd::tx_metadata_t &metadata, + const double timeout + ){ + //translate the metadata to vrt if packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_DATA; + if_packet_info.has_sid = false; + if_packet_info.has_cid = false; + if_packet_info.has_tlr = true; + if_packet_info.has_tsi = false; + if_packet_info.has_tsf = metadata.has_time_spec; + if_packet_info.tsf = metadata.time_spec.to_ticks(_tick_rate); + if_packet_info.sob = metadata.start_of_burst; + if_packet_info.eob = metadata.end_of_burst; + + if (nsamps_per_buff <= _max_samples_per_packet){ + + //TODO remove this code when sample counts of zero are supported by hardware + #ifndef SSPH_DONT_PAD_TO_ONE + if (nsamps_per_buff == 0) return send_one_packet( + _zero_buffs, 1, if_packet_info, timeout + ) & 0x0; + #endif + + return send_one_packet(buffs, nsamps_per_buff, if_packet_info, timeout); + } + size_t total_num_samps_sent = 0; + + //false until final fragment + if_packet_info.eob = false; + + const size_t num_fragments = (nsamps_per_buff-1)/_max_samples_per_packet; + const size_t final_length = ((nsamps_per_buff-1)%_max_samples_per_packet)+1; + + //loop through the following fragment indexes + for (size_t i = 0; i < num_fragments; i++){ + + //send a fragment with the helper function + const size_t num_samps_sent = send_one_packet( + buffs, _max_samples_per_packet, + if_packet_info, timeout, + total_num_samps_sent*_bytes_per_cpu_item + ); + total_num_samps_sent += num_samps_sent; + if (num_samps_sent == 0) return total_num_samps_sent; + + //setup metadata for the next fragment + const time_spec_t time_spec = metadata.time_spec + time_spec_t::from_ticks(total_num_samps_sent, _samp_rate); + if_packet_info.tsf = time_spec.to_ticks(_tick_rate); + if_packet_info.sob = false; + + } + + //send the final fragment with the helper function + if_packet_info.eob = metadata.end_of_burst; + return total_num_samps_sent + send_one_packet( + buffs, final_length, + if_packet_info, timeout, + total_num_samps_sent*_bytes_per_cpu_item + ); + } + +private: + + vrt_packer_type _vrt_packer; + size_t _header_offset_words32; + double _tick_rate, _samp_rate; + struct xport_chan_props_type{ + get_buff_type get_buff; + }; + std::vector<xport_chan_props_type> _props; + std::vector<const void *> _io_buffs; //used in conversion + size_t _bytes_per_otw_item; //used in conversion + size_t _bytes_per_cpu_item; //used in conversion + uhd::convert::converter::sptr _converter; //used in conversion + size_t _max_samples_per_packet; + std::vector<const void *> _zero_buffs; + size_t _next_packet_seq; + + /******************************************************************* + * Send a single packet: + ******************************************************************/ + UHD_INLINE size_t send_one_packet( + const uhd::tx_streamer::buffs_type &buffs, + const size_t nsamps_per_buff, + vrt::if_packet_info_t &if_packet_info, + const double timeout, + const size_t buffer_offset_bytes = 0 + ){ + //load the rest of the if_packet_info in here + if_packet_info.num_payload_bytes = nsamps_per_buff*_io_buffs.size()*_bytes_per_otw_item; + if_packet_info.num_payload_words32 = (if_packet_info.num_payload_bytes + 3/*round up*/)/sizeof(boost::uint32_t); + if_packet_info.packet_count = _next_packet_seq; + + size_t buff_index = 0; + BOOST_FOREACH(xport_chan_props_type &props, _props){ + managed_send_buffer::sptr buff = props.get_buff(timeout); + if (buff.get() == NULL) return 0; //timeout + + //fill a vector with pointers to the io buffers + BOOST_FOREACH(const void *&io_buff, _io_buffs){ + io_buff = reinterpret_cast<const char *>(buffs[buff_index++]) + buffer_offset_bytes; + } + boost::uint32_t *otw_mem = buff->cast<boost::uint32_t *>() + _header_offset_words32; + + //pack metadata into a vrt header + _vrt_packer(otw_mem, if_packet_info); + otw_mem += if_packet_info.num_header_words32; + + //copy-convert the samples into the send buffer + _converter->conv(_io_buffs, otw_mem, nsamps_per_buff); + + //commit the samples to the zero-copy interface + size_t num_bytes_total = (_header_offset_words32+if_packet_info.num_packet_words32)*sizeof(boost::uint32_t); + buff->commit(num_bytes_total); + + } + _next_packet_seq++; //increment sequence after commits + return nsamps_per_buff; + } +}; + +class send_packet_streamer : public send_packet_handler, public tx_streamer{ +public: + send_packet_streamer(const size_t max_num_samps){ + _max_num_samps = max_num_samps; + this->set_max_samples_per_packet(_max_num_samps); + } + + size_t get_num_channels(void) const{ + return this->size(); + } + + size_t get_max_num_samps(void) const{ + return _max_num_samps; + } + + size_t send( + const tx_streamer::buffs_type &buffs, + const size_t nsamps_per_buff, + const uhd::tx_metadata_t &metadata, + const double timeout + ){ + return send_packet_handler::send(buffs, nsamps_per_buff, metadata, timeout); + } + +private: + size_t _max_num_samps; +}; + +}}} //namespace + +#endif /* INCLUDED_LIBUHD_TRANSPORT_SUPER_SEND_PACKET_HANDLER_HPP */ diff --git a/host/lib/transport/udp_common.hpp b/host/lib/transport/udp_common.hpp new file mode 100644 index 000000000..bf4712613 --- /dev/null +++ b/host/lib/transport/udp_common.hpp @@ -0,0 +1,60 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_VRT_PACKET_HANDLER_HPP +#define INCLUDED_LIBUHD_TRANSPORT_VRT_PACKET_HANDLER_HPP + +#include <uhd/config.hpp> +#include <boost/asio.hpp> + +namespace uhd{ namespace transport{ + + typedef boost::shared_ptr<boost::asio::ip::udp::socket> socket_sptr; + + /*! + * Wait for the socket to become ready for a receive operation. + * \param sock_fd the open socket file descriptor + * \param timeout the timeout duration in seconds + * \return true when the socket is ready for receive + */ + UHD_INLINE bool wait_for_recv_ready(int sock_fd, double timeout){ + //setup timeval for timeout + timeval tv; + //If the tv_usec > 1 second on some platforms, select will + //error EINVAL: An invalid timeout interval was specified. + tv.tv_sec = int(timeout); + tv.tv_usec = int(timeout*1000000)%1000000; + + //setup rset for timeout + fd_set rset; + FD_ZERO(&rset); + FD_SET(sock_fd, &rset); + + //http://www.gnu.org/s/hello/manual/libc/Interrupted-Primitives.html + //This macro is provided with gcc to properly deal with EINTR. + //If not provided, define an empty macro, assume that is OK + #ifndef TEMP_FAILURE_RETRY + #define TEMP_FAILURE_RETRY(x) (x) + #endif + + //call select with timeout on receive socket + return TEMP_FAILURE_RETRY(::select(sock_fd+1, &rset, NULL, NULL, &tv)) > 0; + } + +}} //namespace uhd::transport + +#endif /* INCLUDED_LIBUHD_TRANSPORT_VRT_PACKET_HANDLER_HPP */ diff --git a/host/lib/transport/udp_simple.cpp b/host/lib/transport/udp_simple.cpp new file mode 100644 index 000000000..d6c55eae7 --- /dev/null +++ b/host/lib/transport/udp_simple.cpp @@ -0,0 +1,134 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "udp_common.hpp" +#include <uhd/transport/udp_simple.hpp> +#include <uhd/utils/log.hpp> +#include <boost/format.hpp> + +using namespace uhd::transport; +namespace asio = boost::asio; + +/*********************************************************************** + * UDP simple implementation: connected and broadcast + **********************************************************************/ +class udp_simple_impl : public udp_simple{ +public: + udp_simple_impl( + const std::string &addr, const std::string &port, bool bcast, bool connect + ):_connected(connect){ + UHD_LOG << boost::format("Creating udp transport for %s %s") % addr % port << std::endl; + + //resolve the address + asio::ip::udp::resolver resolver(_io_service); + asio::ip::udp::resolver::query query(asio::ip::udp::v4(), addr, port); + _send_endpoint = *resolver.resolve(query); + + //create and open the socket + _socket = socket_sptr(new asio::ip::udp::socket(_io_service)); + _socket->open(asio::ip::udp::v4()); + + //allow broadcasting + _socket->set_option(asio::socket_base::broadcast(bcast)); + + //connect the socket + if (connect) _socket->connect(_send_endpoint); + + } + + size_t send(const asio::const_buffer &buff){ + if (_connected) return _socket->send(asio::buffer(buff)); + return _socket->send_to(asio::buffer(buff), _send_endpoint); + } + + size_t recv(const asio::mutable_buffer &buff, double timeout){ + if (not wait_for_recv_ready(_socket->native(), timeout)) return 0; + return _socket->receive_from(asio::buffer(buff), _recv_endpoint); + } + + std::string get_recv_addr(void){ + return _recv_endpoint.address().to_string(); + } + +private: + bool _connected; + asio::io_service _io_service; + socket_sptr _socket; + asio::ip::udp::endpoint _send_endpoint; + asio::ip::udp::endpoint _recv_endpoint; +}; + +/*********************************************************************** + * UDP public make functions + **********************************************************************/ +udp_simple::sptr udp_simple::make_connected( + const std::string &addr, const std::string &port +){ + return sptr(new udp_simple_impl(addr, port, false, true /* no bcast, connect */)); +} + +udp_simple::sptr udp_simple::make_broadcast( + const std::string &addr, const std::string &port +){ + return sptr(new udp_simple_impl(addr, port, true, false /* bcast, no connect */)); +} + +/*********************************************************************** + * Simple UART over UDP + **********************************************************************/ +#include <boost/thread/thread.hpp> +class udp_simple_uart_impl : public uhd::uart_iface{ +public: + udp_simple_uart_impl(udp_simple::sptr udp){ + _udp = udp; + _len = 0; + _off = 0; + this->write_uart(""); //send an empty packet to init + } + + void write_uart(const std::string &buf){ + _udp->send(asio::buffer(buf)); + } + + std::string read_uart(double timeout){ + std::string line; + const boost::system_time exit_time = boost::get_system_time() + boost::posix_time::milliseconds(long(timeout*1000)); + do{ + //drain anything in current buffer + while (_off < _len){ + const char ch = _buf[_off]; _off++; + line += std::string(1, ch); + if (ch == '\n' or ch == '\r') return line; + } + + //recv a new packet into the buffer + _len = _udp->recv(asio::buffer(_buf), std::max((exit_time - boost::get_system_time()).total_milliseconds()/1000., 0.0)); + _off = 0; + + } while (_len != 0); + return line; + } + +private: + udp_simple::sptr _udp; + size_t _len, _off; + boost::uint8_t _buf[udp_simple::mtu]; +}; + +uhd::uart_iface::sptr udp_simple::make_uart(sptr udp){ + return uart_iface::sptr(new udp_simple_uart_impl(udp)); +} diff --git a/host/lib/transport/udp_zero_copy.cpp b/host/lib/transport/udp_zero_copy.cpp new file mode 100644 index 000000000..e43a96dd0 --- /dev/null +++ b/host/lib/transport/udp_zero_copy.cpp @@ -0,0 +1,312 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "udp_common.hpp" +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/udp_simple.hpp> //mtu +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <boost/format.hpp> +#include <list> + +using namespace uhd; +using namespace uhd::transport; +namespace asio = boost::asio; + +//A reasonable number of frames for send/recv and async/sync +static const size_t DEFAULT_NUM_FRAMES = 32; + +/*********************************************************************** + * Check registry for correct fast-path setting (windows only) + **********************************************************************/ +#ifdef UHD_PLATFORM_WIN32 +#include <atlbase.h> //CRegKey +static void check_registry_for_fast_send_threshold(const size_t mtu){ + static bool warned = false; + if (warned) return; //only allow one printed warning per process + + CRegKey reg_key; + DWORD threshold = 1024; //system default when threshold is not specified + if ( + reg_key.Open(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\AFD\\Parameters", KEY_READ) != ERROR_SUCCESS or + reg_key.QueryDWORDValue("FastSendDatagramThreshold", threshold) != ERROR_SUCCESS or threshold < mtu + ){ + UHD_MSG(warning) << boost::format( + "The MTU (%d) is larger than the FastSendDatagramThreshold (%d)!\n" + "This will negatively affect the transmit performance.\n" + "See the transport application notes for more detail.\n" + ) % mtu % threshold << std::endl; + warned = true; + } + reg_key.Close(); +} +#endif /*UHD_PLATFORM_WIN32*/ + +/*********************************************************************** + * Reusable managed receiver buffer: + * - Initialize with memory and a release callback. + * - Call get new with a length in bytes to re-use. + **********************************************************************/ +class udp_zero_copy_asio_mrb : public managed_recv_buffer{ +public: + udp_zero_copy_asio_mrb(void *mem, bounded_buffer<udp_zero_copy_asio_mrb *> &pending): + _mem(mem), _len(0), _pending(pending){/* NOP */} + + void release(void){ + if (_len == 0) return; + _pending.push_with_haste(this); + _len = 0; + } + + sptr get_new(size_t len){ + _len = len; + return make_managed_buffer(this); + } + + template <class T> T cast(void) const{return static_cast<T>(_mem);} + +private: + const void *get_buff(void) const{return _mem;} + size_t get_size(void) const{return _len;} + + void *_mem; + size_t _len; + bounded_buffer<udp_zero_copy_asio_mrb *> &_pending; +}; + +/*********************************************************************** + * Reusable managed send buffer: + * - Initialize with memory and a commit callback. + * - Call get new with a length in bytes to re-use. + **********************************************************************/ +class udp_zero_copy_asio_msb : public managed_send_buffer{ +public: + udp_zero_copy_asio_msb(void *mem, bounded_buffer<udp_zero_copy_asio_msb *> &pending, int sock_fd): + _mem(mem), _len(0), _pending(pending), _sock_fd(sock_fd){/* NOP */} + + void commit(size_t len){ + if (_len == 0) return; + ::send(_sock_fd, this->cast<const char *>(), len, 0); + _pending.push_with_haste(this); + _len = 0; + } + + sptr get_new(size_t len){ + _len = len; + return make_managed_buffer(this); + } + +private: + void *get_buff(void) const{return _mem;} + size_t get_size(void) const{return _len;} + + void *_mem; + size_t _len; + bounded_buffer<udp_zero_copy_asio_msb *> &_pending; + int _sock_fd; +}; + +/*********************************************************************** + * Zero Copy UDP implementation with ASIO: + * This is the portable zero copy implementation for systems + * where a faster, platform specific solution is not available. + * However, it is not a true zero copy implementation as each + * send and recv requires a copy operation to/from userspace. + **********************************************************************/ +class udp_zero_copy_asio_impl : public udp_zero_copy{ +public: + typedef boost::shared_ptr<udp_zero_copy_asio_impl> sptr; + + udp_zero_copy_asio_impl( + const std::string &addr, + const std::string &port, + const device_addr_t &hints + ): + _recv_frame_size(size_t(hints.cast<double>("recv_frame_size", udp_simple::mtu))), + _num_recv_frames(size_t(hints.cast<double>("num_recv_frames", DEFAULT_NUM_FRAMES))), + _send_frame_size(size_t(hints.cast<double>("send_frame_size", udp_simple::mtu))), + _num_send_frames(size_t(hints.cast<double>("num_send_frames", DEFAULT_NUM_FRAMES))), + _recv_buffer_pool(buffer_pool::make(_num_recv_frames, _recv_frame_size)), + _send_buffer_pool(buffer_pool::make(_num_send_frames, _send_frame_size)), + _pending_recv_buffs(_num_recv_frames), + _pending_send_buffs(_num_send_frames) + { + UHD_LOG << boost::format("Creating udp transport for %s %s") % addr % port << std::endl; + + #ifdef UHD_PLATFORM_WIN32 + check_registry_for_fast_send_threshold(this->get_send_frame_size()); + #endif /*UHD_PLATFORM_WIN32*/ + + //resolve the address + asio::ip::udp::resolver resolver(_io_service); + asio::ip::udp::resolver::query query(asio::ip::udp::v4(), addr, port); + asio::ip::udp::endpoint receiver_endpoint = *resolver.resolve(query); + + //create, open, and connect the socket + _socket = socket_sptr(new asio::ip::udp::socket(_io_service)); + _socket->open(asio::ip::udp::v4()); + _socket->connect(receiver_endpoint); + _sock_fd = _socket->native(); + + //allocate re-usable managed receive buffers + for (size_t i = 0; i < get_num_recv_frames(); i++){ + _mrb_pool.push_back(udp_zero_copy_asio_mrb( + _recv_buffer_pool->at(i), _pending_recv_buffs + )); + _pending_recv_buffs.push_with_haste(&_mrb_pool.back()); + } + + //allocate re-usable managed send buffers + for (size_t i = 0; i < get_num_send_frames(); i++){ + _msb_pool.push_back(udp_zero_copy_asio_msb( + _send_buffer_pool->at(i), _pending_send_buffs, _sock_fd + )); + _pending_send_buffs.push_with_haste(&_msb_pool.back()); + } + } + + //get size for internal socket buffer + template <typename Opt> size_t get_buff_size(void) const{ + Opt option; + _socket->get_option(option); + return option.value(); + } + + //set size for internal socket buffer + template <typename Opt> size_t resize_buff(size_t num_bytes){ + Opt option(num_bytes); + _socket->set_option(option); + return get_buff_size<Opt>(); + } + + /******************************************************************* + * Receive implementation: + * + * Perform a non-blocking receive for performance, + * and then fall back to a blocking receive with timeout. + * Return the managed receive buffer with the new length. + * When the caller is finished with the managed buffer, + * the managed receive buffer is released back into the queue. + ******************************************************************/ + managed_recv_buffer::sptr get_recv_buff(double timeout){ + udp_zero_copy_asio_mrb *mrb = NULL; + if (_pending_recv_buffs.pop_with_timed_wait(mrb, timeout)){ + + #ifdef MSG_DONTWAIT //try a non-blocking recv() if supported + ssize_t ret = ::recv(_sock_fd, mrb->cast<char *>(), _recv_frame_size, MSG_DONTWAIT); + if (ret > 0) return mrb->get_new(ret); + #endif + + if (wait_for_recv_ready(_sock_fd, timeout)) return mrb->get_new( + ::recv(_sock_fd, mrb->cast<char *>(), _recv_frame_size, 0) + ); + + _pending_recv_buffs.push_with_haste(mrb); //timeout: return the managed buffer to the queue + } + return managed_recv_buffer::sptr(); + } + + size_t get_num_recv_frames(void) const {return _num_recv_frames;} + size_t get_recv_frame_size(void) const {return _recv_frame_size;} + + /******************************************************************* + * Send implementation: + * + * Get a managed receive buffer immediately with max length set. + * The caller will fill the buffer and commit it when finished. + * The commit routine will perform a blocking send operation, + * and push the managed send buffer back into the queue. + ******************************************************************/ + managed_send_buffer::sptr get_send_buff(double timeout){ + udp_zero_copy_asio_msb *msb = NULL; + if (_pending_send_buffs.pop_with_timed_wait(msb, timeout)){ + return msb->get_new(_send_frame_size); + } + return managed_send_buffer::sptr(); + } + + size_t get_num_send_frames(void) const {return _num_send_frames;} + size_t get_send_frame_size(void) const {return _send_frame_size;} + +private: + //memory management -> buffers and fifos + const size_t _recv_frame_size, _num_recv_frames; + const size_t _send_frame_size, _num_send_frames; + buffer_pool::sptr _recv_buffer_pool, _send_buffer_pool; + bounded_buffer<udp_zero_copy_asio_mrb *> _pending_recv_buffs; + bounded_buffer<udp_zero_copy_asio_msb *> _pending_send_buffs; + std::list<udp_zero_copy_asio_msb> _msb_pool; + std::list<udp_zero_copy_asio_mrb> _mrb_pool; + + //asio guts -> socket and service + asio::io_service _io_service; + socket_sptr _socket; + int _sock_fd; +}; + +/*********************************************************************** + * UDP zero copy make function + **********************************************************************/ +template<typename Opt> static void resize_buff_helper( + udp_zero_copy_asio_impl::sptr udp_trans, + const size_t target_size, + const std::string &name +){ + std::string help_message; + #if defined(UHD_PLATFORM_LINUX) + help_message = str(boost::format( + "Please run: sudo sysctl -w net.core.%smem_max=%d\n" + ) % ((name == "recv")?"r":"w") % target_size); + #endif /*defined(UHD_PLATFORM_LINUX)*/ + + //resize the buffer if size was provided + if (target_size > 0){ + size_t actual_size = udp_trans->resize_buff<Opt>(target_size); + UHD_LOG << boost::format( + "Target %s sock buff size: %d bytes\n" + "Actual %s sock buff size: %d bytes" + ) % name % target_size % name % actual_size << std::endl; + if (actual_size < target_size) UHD_MSG(warning) << boost::format( + "The %s buffer could not be resized sufficiently.\n" + "Target sock buff size: %d bytes.\n" + "Actual sock buff size: %d bytes.\n" + "See the transport application notes on buffer resizing.\n%s" + ) % name % target_size % actual_size % help_message; + } +} + +udp_zero_copy::sptr udp_zero_copy::make( + const std::string &addr, + const std::string &port, + const device_addr_t &hints +){ + udp_zero_copy_asio_impl::sptr udp_trans( + new udp_zero_copy_asio_impl(addr, port, hints) + ); + + //extract buffer size hints from the device addr + size_t recv_buff_size = size_t(hints.cast<double>("recv_buff_size", 0.0)); + size_t send_buff_size = size_t(hints.cast<double>("send_buff_size", 0.0)); + + //call the helper to resize send and recv buffers + resize_buff_helper<asio::socket_base::receive_buffer_size>(udp_trans, recv_buff_size, "recv"); + resize_buff_helper<asio::socket_base::send_buffer_size> (udp_trans, send_buff_size, "send"); + + return udp_trans; +} diff --git a/host/lib/transport/usb_dummy_impl.cpp b/host/lib/transport/usb_dummy_impl.cpp new file mode 100644 index 000000000..7be753f76 --- /dev/null +++ b/host/lib/transport/usb_dummy_impl.cpp @@ -0,0 +1,38 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/usb_device_handle.hpp> +#include <uhd/transport/usb_control.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/exception.hpp> + +using namespace uhd; +using namespace uhd::transport; + +std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list(boost::uint16_t, boost::uint16_t){ + return std::vector<usb_device_handle::sptr>(); //empty list +} + +usb_control::sptr usb_control::make(usb_device_handle::sptr, const size_t){ + throw uhd::not_implemented_error("no usb support -> usb_control::make not implemented"); +} + +usb_zero_copy::sptr usb_zero_copy::make( + usb_device_handle::sptr, const size_t, const size_t, const size_t, const size_t, const device_addr_t & +){ + throw uhd::not_implemented_error("no usb support -> usb_zero_copy::make not implemented"); +} diff --git a/host/lib/transport/usb_zero_copy_wrapper.cpp b/host/lib/transport/usb_zero_copy_wrapper.cpp new file mode 100644 index 000000000..690e5aaa2 --- /dev/null +++ b/host/lib/transport/usb_zero_copy_wrapper.cpp @@ -0,0 +1,198 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/foreach.hpp> +#include <vector> +#include <iostream> + +using namespace uhd::transport; + +/*********************************************************************** + * USB zero copy wrapper - managed receive buffer + **********************************************************************/ +class usb_zero_copy_wrapper_mrb : public managed_recv_buffer{ +public: + usb_zero_copy_wrapper_mrb(bounded_buffer<usb_zero_copy_wrapper_mrb *> &queue): + _queue(queue){/*NOP*/} + + void release(void){ + if (_mrb.get() == NULL) return; + _mrb->release(); + _queue.push_with_haste(this); + _mrb.reset(); + } + + UHD_INLINE sptr get_new(managed_recv_buffer::sptr mrb, const void *mem, size_t len){ + _mrb = mrb; + _mem = mem; + _len = len; + return make_managed_buffer(this); + } + +private: + const void *get_buff(void) const{return _mem;} + size_t get_size(void) const{return _len;} + + bounded_buffer<usb_zero_copy_wrapper_mrb *> &_queue; + const void *_mem; + size_t _len; + managed_recv_buffer::sptr _mrb; +}; + +/*********************************************************************** + * USB zero copy wrapper - managed send buffer + **********************************************************************/ +class usb_zero_copy_wrapper_msb : public managed_send_buffer{ +public: + usb_zero_copy_wrapper_msb(const usb_zero_copy::sptr internal, const size_t fragmentation_size): + _internal(internal), _fragmentation_size(fragmentation_size){/*NOP*/} + + void commit(size_t len){ + if (len == 0) return; + + //get a reference to the VITA header before incrementing + const boost::uint32_t vita_header = reinterpret_cast<const boost::uint32_t *>(_mem_buffer_tip)[0]; + + _bytes_in_buffer += len; + _mem_buffer_tip += len; + + //extract VITA end of packet flag, we must force flush under eof conditions + const bool eop = (uhd::wtohx(vita_header) & (0x1 << 24)) != 0; + const bool full = _bytes_in_buffer >= (_last_send_buff->size() - _fragmentation_size); + if (eop or full){ + _last_send_buff->commit(_bytes_in_buffer); + _last_send_buff.reset(); + } + } + + UHD_INLINE sptr get_new(const double timeout){ + if (not _last_send_buff){ + _last_send_buff = _internal->get_send_buff(timeout); + if (not _last_send_buff) return sptr(); + _mem_buffer_tip = _last_send_buff->cast<char *>(); + _bytes_in_buffer = 0; + } + return make_managed_buffer(this); + } + +private: + void *get_buff(void) const{return reinterpret_cast<void *>(_mem_buffer_tip);} + size_t get_size(void) const{return _fragmentation_size;} + + usb_zero_copy::sptr _internal; + const size_t _fragmentation_size; + managed_send_buffer::sptr _last_send_buff; + size_t _bytes_in_buffer; + char *_mem_buffer_tip; +}; + +/*********************************************************************** + * USB zero copy wrapper implementation + **********************************************************************/ +class usb_zero_copy_wrapper : public usb_zero_copy{ +public: + usb_zero_copy_wrapper(sptr usb_zc, const size_t frame_boundary): + _internal_zc(usb_zc), + _frame_boundary(frame_boundary), + _available_recv_buffs(this->get_num_recv_frames()), + _mrb_pool(this->get_num_recv_frames(), usb_zero_copy_wrapper_mrb(_available_recv_buffs)), + _the_only_msb(usb_zero_copy_wrapper_msb(usb_zc, frame_boundary)) + { + BOOST_FOREACH(usb_zero_copy_wrapper_mrb &mrb, _mrb_pool){ + _available_recv_buffs.push_with_haste(&mrb); + } + } + + managed_recv_buffer::sptr get_recv_buff(double timeout){ + //attempt to get a managed recv buffer + if (not _last_recv_buff.get()){ + _last_recv_buff = _internal_zc->get_recv_buff(timeout); + _last_recv_offset = 0; + } + + //attempt to get a wrapper for a managed recv buffer + usb_zero_copy_wrapper_mrb *wmrb = NULL; + if (_last_recv_buff.get() and _available_recv_buffs.pop_with_timed_wait(wmrb, timeout)){ + //extract this packet's memory address and length in bytes + const char *mem = _last_recv_buff->cast<const char *>() + _last_recv_offset; + const boost::uint32_t *mem32 = reinterpret_cast<const boost::uint32_t *>(mem); + const size_t len = (uhd::wtohx(mem32[0]) & 0xffff)*sizeof(boost::uint32_t); //length in bytes (from VRT header) + + managed_recv_buffer::sptr recv_buff; //the buffer to be returned to the user + recv_buff = wmrb->get_new(_last_recv_buff, mem, len); + _last_recv_offset += len; + + //check if this receive buffer has been exhausted + if (_last_recv_offset >= _last_recv_buff->size()) { + _last_recv_buff.reset(); + } + + return recv_buff; + } + + //otherwise return a null sptr for failure + return managed_recv_buffer::sptr(); + } + + size_t get_num_recv_frames(void) const{ + return _internal_zc->get_num_recv_frames(); + } + + size_t get_recv_frame_size(void) const{ + return std::min(_frame_boundary, _internal_zc->get_recv_frame_size()); + } + + managed_send_buffer::sptr get_send_buff(double timeout){ + return _the_only_msb.get_new(timeout); + } + + size_t get_num_send_frames(void) const{ + return _internal_zc->get_num_send_frames(); + } + + size_t get_send_frame_size(void) const{ + return std::min(_frame_boundary, _internal_zc->get_send_frame_size()); + } + +private: + sptr _internal_zc; + size_t _frame_boundary; + bounded_buffer<usb_zero_copy_wrapper_mrb *> _available_recv_buffs; + std::vector<usb_zero_copy_wrapper_mrb> _mrb_pool; + usb_zero_copy_wrapper_msb _the_only_msb; + + //buffer to store partially-received VRT packets in + buffer_pool::sptr _fragment_mem; + + //state for last recv buffer to create multiple managed buffers + managed_recv_buffer::sptr _last_recv_buff; + size_t _last_recv_offset; +}; + +/*********************************************************************** + * USB zero copy wrapper factory function + **********************************************************************/ +usb_zero_copy::sptr usb_zero_copy::make_wrapper( + sptr usb_zc, size_t usb_frame_boundary +){ + return sptr(new usb_zero_copy_wrapper(usb_zc, usb_frame_boundary)); +} diff --git a/host/lib/types/CMakeLists.txt b/host/lib/types/CMakeLists.txt new file mode 100644 index 000000000..2ca0faef7 --- /dev/null +++ b/host/lib/types/CMakeLists.txt @@ -0,0 +1,91 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Setup defines for high resolution timing +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring high resolution timing...") +INCLUDE(CheckCXXSourceCompiles) + +SET(CMAKE_REQUIRED_LIBRARIES -lrt) +CHECK_CXX_SOURCE_COMPILES(" + #include <ctime> + int main(){ + timespec ts; + return clock_gettime(CLOCK_MONOTONIC, &ts); + } + " HAVE_CLOCK_GETTIME +) +UNSET(CMAKE_REQUIRED_LIBRARIES) + +INCLUDE(CheckCXXSourceCompiles) +CHECK_CXX_SOURCE_COMPILES(" + #include <mach/mach_time.h> + int main(){ + mach_timebase_info_data_t info; + mach_timebase_info(&info); + mach_absolute_time(); + return 0; + } + " HAVE_MACH_ABSOLUTE_TIME +) + +CHECK_CXX_SOURCE_COMPILES(" + #include <Windows.h> + int main(){ + LARGE_INTEGER value; + QueryPerformanceCounter(&value); + QueryPerformanceFrequency(&value); + return 0; + } + " HAVE_QUERY_PERFORMANCE_COUNTER +) + +IF(HAVE_CLOCK_GETTIME) + MESSAGE(STATUS " High resolution timing supported through clock_gettime.") + SET(TIME_SPEC_DEFS HAVE_CLOCK_GETTIME) + LIBUHD_APPEND_LIBS("-lrt") +ELSEIF(HAVE_MACH_ABSOLUTE_TIME) + MESSAGE(STATUS " High resolution timing supported through mach_absolute_time.") + SET(TIME_SPEC_DEFS HAVE_MACH_ABSOLUTE_TIME) +ELSEIF(HAVE_QUERY_PERFORMANCE_COUNTER) + MESSAGE(STATUS " High resolution timing supported through QueryPerformanceCounter.") + SET(TIME_SPEC_DEFS HAVE_QUERY_PERFORMANCE_COUNTER) +ELSE() + MESSAGE(STATUS " High resolution timing supported though microsec_clock.") + SET(TIME_SPEC_DEFS HAVE_MICROSEC_CLOCK) +ENDIF() + +SET_SOURCE_FILES_PROPERTIES( + ${CMAKE_CURRENT_SOURCE_DIR}/time_spec.cpp + PROPERTIES COMPILE_DEFINITIONS "${TIME_SPEC_DEFS}" +) + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/device_addr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mac_addr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ranges.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sensors.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/serial.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/time_spec.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tune.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/types.cpp +) diff --git a/host/lib/types/device_addr.cpp b/host/lib/types/device_addr.cpp new file mode 100644 index 000000000..1554c3e4e --- /dev/null +++ b/host/lib/types/device_addr.cpp @@ -0,0 +1,133 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/device_addr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/tokenizer.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/regex.hpp> +#include <stdexcept> +#include <sstream> + +using namespace uhd; + +static const std::string arg_delim = ","; +static const std::string pair_delim = "="; + +static std::string trim(const std::string &in){ + return boost::algorithm::trim_copy(in); +} + +#define tokenizer(inp, sep) \ + boost::tokenizer<boost::char_separator<char> > \ + (inp, boost::char_separator<char>(sep.c_str())) + +device_addr_t::device_addr_t(const std::string &args){ + BOOST_FOREACH(const std::string &pair, tokenizer(args, arg_delim)){ + if (trim(pair) == "") continue; + std::vector<std::string> toks; + BOOST_FOREACH(const std::string &tok, tokenizer(pair, pair_delim)){ + toks.push_back(tok); + } + if (toks.size() == 1) toks.push_back(""); //pad empty value + if (toks.size() == 2 and not trim(toks[0]).empty()){ //only valid combination + this->set(trim(toks[0]), trim(toks[1])); + } + else throw uhd::value_error("invalid args string: "+args); //otherwise error + } +} + +std::string device_addr_t::to_pp_string(void) const{ + if (this->size() == 0) return "Empty Device Address"; + + std::stringstream ss; + ss << "Device Address:" << std::endl; + BOOST_FOREACH(std::string key, this->keys()){ + ss << boost::format(" %s: %s") % key % this->get(key) << std::endl; + } + return ss.str(); +} + +std::string device_addr_t::to_string(void) const{ + std::string args_str; + size_t count = 0; + BOOST_FOREACH(const std::string &key, this->keys()){ + args_str += ((count++)? arg_delim : "") + key + pair_delim + this->get(key); + } + return args_str; +} + +#include <uhd/utils/msg.hpp> + +device_addrs_t uhd::separate_device_addr(const device_addr_t &dev_addr){ + //------------ support old deprecated way and print warning -------- + if (dev_addr.has_key("addr") and not dev_addr["addr"].empty()){ + std::vector<std::string> addrs; boost::split(addrs, dev_addr["addr"], boost::is_any_of(" ")); + if (addrs.size() > 1){ + device_addr_t fixed_dev_addr = dev_addr; + fixed_dev_addr.pop("addr"); + for (size_t i = 0; i < addrs.size(); i++){ + fixed_dev_addr[str(boost::format("addr%d") % i)] = addrs[i]; + } + UHD_MSG(warning) << + "addr = <space separated list of ip addresses> is deprecated.\n" + "To address a multi-device, use multiple <key><index> = <val>.\n" + "See the USRP-NXXX application notes. Two device example:\n" + " addr0 = 192.168.10.2\n" + " addr1 = 192.168.10.3\n" + ; + return separate_device_addr(fixed_dev_addr); + } + } + //------------------------------------------------------------------ + device_addrs_t dev_addrs(1); //must be at least one (obviously) + std::vector<std::string> global_keys; //keys that apply to all (no numerical suffix) + BOOST_FOREACH(const std::string &key, dev_addr.keys()){ + boost::cmatch matches; + if (not boost::regex_match(key.c_str(), matches, boost::regex("^(\\D+)(\\d*)$"))){ + throw std::runtime_error("unknown key format: " + key); + } + std::string key_part(matches[1].first, matches[1].second); + std::string num_part(matches[2].first, matches[2].second); + if (num_part.empty()){ //no number? save it for later + global_keys.push_back(key); + continue; + } + const size_t num = boost::lexical_cast<size_t>(num_part); + dev_addrs.resize(std::max(num+1, dev_addrs.size())); + dev_addrs[num][key_part] = dev_addr[key]; + } + + //copy the global settings across all device addresses + BOOST_FOREACH(device_addr_t &my_dev_addr, dev_addrs){ + BOOST_FOREACH(const std::string &global_key, global_keys){ + my_dev_addr[global_key] = dev_addr[global_key]; + } + } + return dev_addrs; +} + +device_addr_t uhd::combine_device_addrs(const device_addrs_t &dev_addrs){ + device_addr_t dev_addr; + for (size_t i = 0; i < dev_addrs.size(); i++){ + BOOST_FOREACH(const std::string &key, dev_addrs[i].keys()){ + dev_addr[str(boost::format("%s%d") % key % i)] = dev_addrs[i][key]; + } + } + return dev_addr; +} diff --git a/host/lib/types/mac_addr.cpp b/host/lib/types/mac_addr.cpp new file mode 100644 index 000000000..a5cb90f97 --- /dev/null +++ b/host/lib/types/mac_addr.cpp @@ -0,0 +1,75 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/mac_addr.hpp> +#include <uhd/exception.hpp> +#include <boost/tokenizer.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/cstdint.hpp> +#include <sstream> + +using namespace uhd; + +mac_addr_t::mac_addr_t(const byte_vector_t &bytes) : _bytes(bytes){ + UHD_ASSERT_THROW(_bytes.size() == 6); +} + +mac_addr_t mac_addr_t::from_bytes(const byte_vector_t &bytes){ + return mac_addr_t(bytes); +} + +mac_addr_t mac_addr_t::from_string(const std::string &mac_addr_str){ + + byte_vector_t bytes; + + try{ + if (mac_addr_str.size() != 17){ + throw uhd::value_error("expected exactly 17 characters"); + } + + //split the mac addr hex string at the colons + boost::tokenizer<boost::char_separator<char> > hex_num_toks( + mac_addr_str, boost::char_separator<char>(":")); + BOOST_FOREACH(const std::string &hex_str, hex_num_toks){ + int hex_num; + std::istringstream iss(hex_str); + iss >> std::hex >> hex_num; + bytes.push_back(boost::uint8_t(hex_num)); + } + + } + catch(std::exception const& e){ + throw uhd::value_error(str( + boost::format("Invalid mac address: %s\n\t%s") % mac_addr_str % e.what() + )); + } + + return mac_addr_t::from_bytes(bytes); +} + +byte_vector_t mac_addr_t::to_bytes(void) const{ + return _bytes; +} + +std::string mac_addr_t::to_string(void) const{ + std::string addr = ""; + BOOST_FOREACH(boost::uint8_t byte, this->to_bytes()){ + addr += str(boost::format("%s%02x") % ((addr == "")?"":":") % int(byte)); + } + return addr; +} diff --git a/host/lib/types/ranges.cpp b/host/lib/types/ranges.cpp new file mode 100644 index 000000000..6e39bc688 --- /dev/null +++ b/host/lib/types/ranges.cpp @@ -0,0 +1,163 @@ +// +// Copyright 2011-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/ranges.hpp> +#include <uhd/exception.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/foreach.hpp> +#include <algorithm> +#include <sstream> + +using namespace uhd; + +/*********************************************************************** + * range_t implementation code + **********************************************************************/ +struct range_t::impl{ + impl(double start, double stop, double step): + start(start), stop(stop), step(step) + { + /* NOP */ + } + double start, stop, step; +}; + +range_t::range_t(double value): + _impl(UHD_PIMPL_MAKE(impl, (value, value, 0))) +{ + /* NOP */ +} + +range_t::range_t( + double start, double stop, double step +): + _impl(UHD_PIMPL_MAKE(impl, (start, stop, step))) +{ + if (stop < start){ + throw uhd::value_error("cannot make range where stop < start"); + } +} + +double range_t::start(void) const{ + return _impl->start; +} + +double range_t::stop(void) const{ + return _impl->stop; +} + +double range_t::step(void) const{ + return _impl->step; +} + +const std::string range_t::to_pp_string(void) const{ + std::stringstream ss; + ss << "(" << this->start(); + if (this->start() != this->stop()) ss << ", " << this->stop(); + if (this->step() != 0) ss << ", " << this->step(); + ss << ")"; + return ss.str(); +} + +/*********************************************************************** + * meta_range_t implementation code + **********************************************************************/ +void check_meta_range_monotonic(const meta_range_t &mr){ + if (mr.empty()){ + throw uhd::value_error("meta-range cannot be empty"); + } + for (size_t i = 1; i < mr.size(); i++){ + if (mr.at(i).start() < mr.at(i-1).stop()){ + throw uhd::value_error("meta-range is not monotonic"); + } + } +} + +meta_range_t::meta_range_t(void){ + /* NOP */ +} + +meta_range_t::meta_range_t( + double start, double stop, double step +): + std::vector<range_t > (1, range_t(start, stop, step)) +{ + /* NOP */ +} + +double meta_range_t::start(void) const{ + check_meta_range_monotonic(*this); + double min_start = this->front().start(); + BOOST_FOREACH(const range_t &r, (*this)){ + min_start = std::min(min_start, r.start()); + } + return min_start; +} + +double meta_range_t::stop(void) const{ + check_meta_range_monotonic(*this); + double max_stop = this->front().stop(); + BOOST_FOREACH(const range_t &r, (*this)){ + max_stop = std::max(max_stop, r.stop()); + } + return max_stop; +} + +double meta_range_t::step(void) const{ + check_meta_range_monotonic(*this); + std::vector<double> non_zero_steps; + range_t last = this->front(); + BOOST_FOREACH(const range_t &r, (*this)){ + //steps at each range + if (r.step() > 0) non_zero_steps.push_back(r.step()); + //and steps in-between ranges + double ibtw_step = r.start() - last.stop(); + if (ibtw_step > 0) non_zero_steps.push_back(ibtw_step); + //store ref to last + last = r; + } + if (non_zero_steps.empty()) return 0; //all zero steps, its zero... + return *std::min_element(non_zero_steps.begin(), non_zero_steps.end()); +} + +double meta_range_t::clip(double value, bool clip_step) const{ + check_meta_range_monotonic(*this); + double last_stop = this->front().stop(); + BOOST_FOREACH(const range_t &r, (*this)){ + //in-between ranges, clip to nearest + if (value < r.start()){ + return (std::abs(value - r.start()) < std::abs(value - last_stop))? + r.start() : last_stop; + } + //in this range, clip here + if (value <= r.stop()){ + if (not clip_step or r.step() == 0) return value; + return boost::math::round((value - r.start())/r.step())*r.step() + r.start(); + } + //continue on to the next range + last_stop = r.stop(); + } + return last_stop; +} + +const std::string meta_range_t::to_pp_string(void) const{ + std::stringstream ss; + BOOST_FOREACH(const range_t &r, (*this)){ + ss << r.to_pp_string() << std::endl; + } + return ss.str(); +} diff --git a/host/lib/types/sensors.cpp b/host/lib/types/sensors.cpp new file mode 100644 index 000000000..52a63d14c --- /dev/null +++ b/host/lib/types/sensors.cpp @@ -0,0 +1,94 @@ +// +// Copyright 2011-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/sensors.hpp> +#include <uhd/exception.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> + +using namespace uhd; + +sensor_value_t::sensor_value_t( + const std::string &name, + bool value, + const std::string &utrue, + const std::string &ufalse +): + name(name), value(value?"true":"false"), + unit(value?utrue:ufalse), type(BOOLEAN) +{ + /* NOP */ +} + +sensor_value_t::sensor_value_t( + const std::string &name, + signed value, + const std::string &unit, + const std::string &formatter +): + name(name), value(str(boost::format(formatter) % value)), + unit(unit), type(INTEGER) +{ + /* NOP */ +} + +sensor_value_t::sensor_value_t( + const std::string &name, + double value, + const std::string &unit, + const std::string &formatter +): + name(name), value(str(boost::format(formatter) % value)), + unit(unit), type(REALNUM) +{ + /* NOP */ +} + +sensor_value_t::sensor_value_t( + const std::string &name, + const std::string &value, + const std::string &unit +): + name(name), value(value), + unit(unit), type(STRING) +{ + /* NOP */ +} + +std::string sensor_value_t::to_pp_string(void) const{ + switch(type){ + case BOOLEAN: + return str(boost::format("%s: %s") % name % unit); + case INTEGER: + case REALNUM: + case STRING: + return str(boost::format("%s: %s %s") % name % value % unit); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +bool sensor_value_t::to_bool(void) const{ + return value == "true"; +} + +signed sensor_value_t::to_int(void) const{ + return boost::lexical_cast<signed>(value); +} + +double sensor_value_t::to_real(void) const{ + return boost::lexical_cast<double>(value); +} diff --git a/host/lib/types/serial.cpp b/host/lib/types/serial.cpp new file mode 100644 index 000000000..9e9d32954 --- /dev/null +++ b/host/lib/types/serial.cpp @@ -0,0 +1,78 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/serial.hpp> +#include <boost/thread.hpp> //for sleeping +#include <boost/assign/list_of.hpp> + +using namespace uhd; + +spi_config_t::spi_config_t(edge_t edge): + mosi_edge(edge), + miso_edge(edge) +{ + /* NOP */ +} + +void i2c_iface::write_eeprom( + boost::uint8_t addr, + boost::uint8_t offset, + const byte_vector_t &bytes +){ + for (size_t i = 0; i < bytes.size(); i++){ + //write a byte at a time, its easy that way + byte_vector_t cmd = boost::assign::list_of(offset+i)(bytes[i]); + this->write_i2c(addr, cmd); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); //worst case write + } +} + +byte_vector_t i2c_iface::read_eeprom( + boost::uint8_t addr, + boost::uint8_t offset, + size_t num_bytes +){ + byte_vector_t bytes; + for (size_t i = 0; i < num_bytes; i++){ + //do a zero byte write to start read cycle + this->write_i2c(addr, byte_vector_t(1, offset+i)); + bytes.push_back(this->read_i2c(addr, 1).at(0)); + } + return bytes; +} + +boost::uint32_t spi_iface::read_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + return transact_spi( + which_slave, config, data, num_bits, true + ); +} + +void spi_iface::write_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + transact_spi( + which_slave, config, data, num_bits, false + ); +} diff --git a/host/lib/types/time_spec.cpp b/host/lib/types/time_spec.cpp new file mode 100644 index 000000000..ec939e52e --- /dev/null +++ b/host/lib/types/time_spec.cpp @@ -0,0 +1,156 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/time_spec.hpp> +#include <inttypes.h> //imaxdiv, intmax_t + +using namespace uhd; + +/*********************************************************************** + * Time spec system time + **********************************************************************/ + +#ifdef HAVE_CLOCK_GETTIME +#include <time.h> +time_spec_t time_spec_t::get_system_time(void){ + timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return time_spec_t(ts.tv_sec, ts.tv_nsec, 1e9); +} +#endif /* HAVE_CLOCK_GETTIME */ + + +#ifdef HAVE_MACH_ABSOLUTE_TIME +#include <mach/mach_time.h> +time_spec_t time_spec_t::get_system_time(void){ + mach_timebase_info_data_t info; mach_timebase_info(&info); + intmax_t nanosecs = mach_absolute_time()*info.numer/info.denom; + return time_spec_t::from_ticks(nanosecs, 1e9); +} +#endif /* HAVE_MACH_ABSOLUTE_TIME */ + + +#ifdef HAVE_QUERY_PERFORMANCE_COUNTER +#include <Windows.h> +time_spec_t time_spec_t::get_system_time(void){ + LARGE_INTEGER counts, freq; + QueryPerformanceCounter(&counts); + QueryPerformanceFrequency(&freq); + return time_spec_t::from_ticks(counts.QuadPart, double(freq.QuadPart)); +} +#endif /* HAVE_QUERY_PERFORMANCE_COUNTER */ + + +#ifdef HAVE_MICROSEC_CLOCK +#include <boost/date_time/posix_time/posix_time.hpp> +namespace pt = boost::posix_time; +time_spec_t time_spec_t::get_system_time(void){ + pt::ptime time_now = pt::microsec_clock::universal_time(); + pt::time_duration time_dur = time_now - pt::from_time_t(0); + return time_spec_t( + time_t(time_dur.total_seconds()), + long(time_dur.fractional_seconds()), + double(pt::time_duration::ticks_per_second()) + ); +} +#endif /* HAVE_MICROSEC_CLOCK */ + +/*********************************************************************** + * Time spec constructors + **********************************************************************/ +#define time_spec_init(full, frac) { \ + const time_t _full = time_t(full); \ + const double _frac = double(frac); \ + const int _frac_int = int(_frac); \ + _full_secs = _full + _frac_int; \ + _frac_secs = _frac - _frac_int; \ + if (_frac_secs < 0) {\ + _full_secs -= 1; \ + _frac_secs += 1; \ + } \ +} + +UHD_INLINE long long fast_llround(const double x){ + return (long long)(x + 0.5); // assumption of non-negativity +} + +time_spec_t::time_spec_t(double secs){ + time_spec_init(0, secs); +} + +time_spec_t::time_spec_t(time_t full_secs, double frac_secs){ + time_spec_init(full_secs, frac_secs); +} + +time_spec_t::time_spec_t(time_t full_secs, long tick_count, double tick_rate){ + const double frac_secs = tick_count/tick_rate; + time_spec_init(full_secs, frac_secs); +} + +time_spec_t time_spec_t::from_ticks(long long ticks, double tick_rate){ + const imaxdiv_t divres = imaxdiv(ticks, fast_llround(tick_rate)); + return time_spec_t(time_t(divres.quot), double(divres.rem)/tick_rate); +} + +/*********************************************************************** + * Time spec accessors + **********************************************************************/ +long time_spec_t::get_tick_count(double tick_rate) const{ + return long(fast_llround(this->get_frac_secs()*tick_rate)); +} + +long long time_spec_t::to_ticks(double tick_rate) const{ + return fast_llround(this->get_frac_secs()*tick_rate) + \ + (this->get_full_secs() * fast_llround(tick_rate)); +} + +double time_spec_t::get_real_secs(void) const{ + return this->get_full_secs() + this->get_frac_secs(); +} + +/*********************************************************************** + * Time spec math overloads + **********************************************************************/ +time_spec_t &time_spec_t::operator+=(const time_spec_t &rhs){ + time_spec_init( + this->get_full_secs() + rhs.get_full_secs(), + this->get_frac_secs() + rhs.get_frac_secs() + ); + return *this; +} + +time_spec_t &time_spec_t::operator-=(const time_spec_t &rhs){ + time_spec_init( + this->get_full_secs() - rhs.get_full_secs(), + this->get_frac_secs() - rhs.get_frac_secs() + ); + return *this; +} + +bool uhd::operator==(const time_spec_t &lhs, const time_spec_t &rhs){ + return + lhs.get_full_secs() == rhs.get_full_secs() and + lhs.get_frac_secs() == rhs.get_frac_secs() + ; +} + +bool uhd::operator<(const time_spec_t &lhs, const time_spec_t &rhs){ + return ( + (lhs.get_full_secs() < rhs.get_full_secs()) or ( + (lhs.get_full_secs() == rhs.get_full_secs()) and + (lhs.get_frac_secs() < rhs.get_frac_secs()) + )); +} diff --git a/host/lib/types/tune.cpp b/host/lib/types/tune.cpp new file mode 100644 index 000000000..154f0990f --- /dev/null +++ b/host/lib/types/tune.cpp @@ -0,0 +1,52 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/tune_request.hpp> +#include <uhd/types/tune_result.hpp> +#include <boost/format.hpp> + +using namespace uhd; + +tune_request_t::tune_request_t(double target_freq): + target_freq(target_freq), + rf_freq_policy(POLICY_AUTO), + dsp_freq_policy(POLICY_AUTO) +{ + /* NOP */ +} + +tune_request_t::tune_request_t(double target_freq, double lo_off): + target_freq(target_freq), + rf_freq_policy(POLICY_MANUAL), + rf_freq(target_freq + lo_off), + dsp_freq_policy(POLICY_AUTO) +{ + /* NOP */ +} + +std::string tune_result_t::to_pp_string(void) const{ + return str(boost::format( + "Tune Result:\n" + " Target RF Freq: %f (MHz)\n" + " Actual RF Freq: %f (MHz)\n" + " Target DSP Freq: %f (MHz)\n" + " Actual DSP Freq: %f (MHz)\n" + ) + % (target_rf_freq/1e6) % (actual_rf_freq/1e6) + % (target_dsp_freq/1e6) % (actual_dsp_freq/1e6) + ); +} diff --git a/host/lib/types/types.cpp b/host/lib/types/types.cpp new file mode 100644 index 000000000..1a3e7e860 --- /dev/null +++ b/host/lib/types/types.cpp @@ -0,0 +1,44 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/metadata.hpp> + +using namespace uhd; + +/*********************************************************************** + * stream command + **********************************************************************/ +stream_cmd_t::stream_cmd_t(const stream_mode_t &stream_mode): + stream_mode(stream_mode), + num_samps(0), + stream_now(true) +{ + /* NOP */ +} + +/*********************************************************************** + * metadata + **********************************************************************/ +tx_metadata_t::tx_metadata_t(void): + has_time_spec(false), + time_spec(time_spec_t()), + start_of_burst(false), + end_of_burst(false) +{ + /* NOP */ +} diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt new file mode 100644 index 000000000..8ae379f73 --- /dev/null +++ b/host/lib/usrp/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_eeprom.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_id.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/gps_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mboard_eeprom.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/subdev_spec.cpp +) + +INCLUDE_SUBDIRECTORY(cores) +INCLUDE_SUBDIRECTORY(dboard) +INCLUDE_SUBDIRECTORY(common) +INCLUDE_SUBDIRECTORY(usrp1) +INCLUDE_SUBDIRECTORY(usrp2) +INCLUDE_SUBDIRECTORY(b100) +INCLUDE_SUBDIRECTORY(e100) diff --git a/host/lib/usrp/README b/host/lib/usrp/README new file mode 100644 index 000000000..344209179 --- /dev/null +++ b/host/lib/usrp/README @@ -0,0 +1,15 @@ +######################################################################## +# lib USRP directories: +######################################################################## + +dboard: + Daughterboard implementation code for all USRP daughterboards + +usrp1: + Implementation code for the USB-based USRP Classic motherboard. + +usrp2: + Implementation code for USRP2, USRP-N200, and USRP-N210. + +usrp_e100: + Implementation code for USRP-E100. diff --git a/host/lib/usrp/b100/CMakeLists.txt b/host/lib/usrp/b100/CMakeLists.txt new file mode 100644 index 000000000..1237f52d1 --- /dev/null +++ b/host/lib/usrp/b100/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the B100 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF) + +IF(ENABLE_B100) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/b100_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/b100_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp + ) +ENDIF(ENABLE_B100) diff --git a/host/lib/usrp/b100/b100_ctrl.cpp b/host/lib/usrp/b100/b100_ctrl.cpp new file mode 100644 index 000000000..e6136c00e --- /dev/null +++ b/host/lib/usrp/b100/b100_ctrl.cpp @@ -0,0 +1,257 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "b100_ctrl.hpp" +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/serial.hpp> +#include "ctrl_packet.hpp" +#include <boost/thread/thread.hpp> +#include <boost/bind.hpp> +#include <uhd/exception.hpp> + +using namespace uhd::transport; +using namespace uhd; + +bool b100_ctrl_debug = false; + +class b100_ctrl_impl : public b100_ctrl { +public: + b100_ctrl_impl(uhd::transport::zero_copy_if::sptr ctrl_transport): + sync_ctrl_fifo(2), + _ctrl_transport(ctrl_transport), + _seq(0) + { + viking_marauder = task::make(boost::bind(&b100_ctrl_impl::viking_marauder_loop, this)); + } + + ~b100_ctrl_impl(void){ + //stop the marauder first so it cant access deconstructed objects + viking_marauder.reset(); + } + + int write(boost::uint32_t addr, const ctrl_data_t &data); + ctrl_data_t read(boost::uint32_t addr, size_t len); + + bool get_ctrl_data(ctrl_data_t &pkt_data, double timeout); + + void poke32(wb_addr_type addr, boost::uint32_t data){ + boost::mutex::scoped_lock lock(_ctrl_mutex); + + ctrl_data_t words(2); + words[0] = data & 0x0000FFFF; + words[1] = data >> 16; + this->write(addr, words); + } + + boost::uint32_t peek32(wb_addr_type addr){ + boost::mutex::scoped_lock lock(_ctrl_mutex); + + ctrl_data_t words = this->read(addr, 2); + return boost::uint32_t((boost::uint32_t(words[1]) << 16) | words[0]); + } + + void poke16(wb_addr_type addr, boost::uint16_t data){ + boost::mutex::scoped_lock lock(_ctrl_mutex); + + ctrl_data_t words(1); + words[0] = data; + this->write(addr, words); + } + + boost::uint16_t peek16(wb_addr_type addr){ + boost::mutex::scoped_lock lock(_ctrl_mutex); + + ctrl_data_t words = this->read(addr, 1); + return boost::uint16_t(words[0]); + } + + void set_async_cb(const async_cb_type &async_cb){ + boost::mutex::scoped_lock lock(_async_mutex); + _async_cb = async_cb; + } + +private: + int send_pkt(boost::uint16_t *cmd); + + //änd hërë wë gö ä-Vïkïng för äsynchronous control packets + void viking_marauder_loop(void); + bounded_buffer<ctrl_data_t> sync_ctrl_fifo; + async_cb_type _async_cb; + task::sptr viking_marauder; + + uhd::transport::zero_copy_if::sptr _ctrl_transport; + boost::uint8_t _seq; + boost::mutex _ctrl_mutex, _async_mutex; +}; + +/*********************************************************************** + * helper functions for packing/unpacking control packets + **********************************************************************/ +void pack_ctrl_pkt(boost::uint16_t *pkt_buff, + const ctrl_pkt_t &pkt){ + //first two bits are OP + //next six bits are CALLBACKS + //next 8 bits are SEQUENCE + //next 16 bits are LENGTH (16-bit word) + //next 32 bits are ADDRESS (16-bit word LSW) + //then DATA (28 16-bit words) + pkt_buff[0] = (boost::uint16_t(pkt.pkt_meta.op) << 14) | (boost::uint16_t(pkt.pkt_meta.callbacks) << 8) | pkt.pkt_meta.seq; + pkt_buff[1] = pkt.pkt_meta.len; + pkt_buff[2] = (pkt.pkt_meta.addr & 0x00000FFF); + pkt_buff[3] = 0x0000; //address high bits always 0 on this device + + for(size_t i = 0; i < pkt.data.size(); i++) { + pkt_buff[4+i] = pkt.data[i]; + } +} + +void unpack_ctrl_pkt(const boost::uint16_t *pkt_buff, + ctrl_pkt_t &pkt){ + pkt.pkt_meta.seq = pkt_buff[0] & 0xFF; + pkt.pkt_meta.op = CTRL_PKT_OP_READ; //really this is useless + pkt.pkt_meta.len = pkt_buff[1]; + pkt.pkt_meta.callbacks = 0; //callbacks aren't implemented yet + pkt.pkt_meta.addr = pkt_buff[2] | boost::uint32_t(pkt_buff[3] << 16); + + //let's check this so we don't go pushing 64K of crap onto the pkt + if(pkt.pkt_meta.len > CTRL_PACKET_DATA_LENGTH) { + throw uhd::runtime_error("Received control packet too long"); + } + + for(int i = 4; i < 4+pkt.pkt_meta.len; i++) pkt.data.push_back(pkt_buff[i]); +} + +int b100_ctrl_impl::send_pkt(boost::uint16_t *cmd) { + managed_send_buffer::sptr sbuf = _ctrl_transport->get_send_buff(); + if(!sbuf.get()) { + throw uhd::runtime_error("Control channel send error"); + } + + //FIXME there's a better way to do this + for(size_t i = 0; i < (CTRL_PACKET_LENGTH / sizeof(boost::uint16_t)); i++) { + sbuf->cast<boost::uint16_t *>()[i] = cmd[i]; + } + sbuf->commit(CTRL_PACKET_LENGTH); //fixed size transaction + return 0; +} + +int b100_ctrl_impl::write(boost::uint32_t addr, const ctrl_data_t &data) { + UHD_ASSERT_THROW(data.size() <= (CTRL_PACKET_DATA_LENGTH / sizeof(boost::uint16_t))); + ctrl_pkt_t pkt; + pkt.data = data; + pkt.pkt_meta.op = CTRL_PKT_OP_WRITE; + pkt.pkt_meta.callbacks = 0; + pkt.pkt_meta.seq = _seq++; + pkt.pkt_meta.len = pkt.data.size(); + pkt.pkt_meta.addr = addr; + boost::uint16_t pkt_buff[CTRL_PACKET_LENGTH / sizeof(boost::uint16_t)] = {}; + + pack_ctrl_pkt(pkt_buff, pkt); + size_t result = send_pkt(pkt_buff); + return result; +} + +ctrl_data_t b100_ctrl_impl::read(boost::uint32_t addr, size_t len) { + UHD_ASSERT_THROW(len <= (CTRL_PACKET_DATA_LENGTH / sizeof(boost::uint16_t))); + + ctrl_pkt_t pkt; + pkt.pkt_meta.op = CTRL_PKT_OP_READ; + pkt.pkt_meta.callbacks = 0; + pkt.pkt_meta.seq = _seq++; + pkt.pkt_meta.len = len; + pkt.pkt_meta.addr = addr; + boost::uint16_t pkt_buff[CTRL_PACKET_LENGTH / sizeof(boost::uint16_t)] = {}; + + //flush anything that might be in the queue + while (get_ctrl_data(pkt.data, 0.0)){ + UHD_MSG(error) << "B100: control read found unexpected packet." << std::endl; + } + + pack_ctrl_pkt(pkt_buff, pkt); + send_pkt(pkt_buff); + + //block with timeout waiting for the response to appear + if (not get_ctrl_data(pkt.data, 0.1)) throw uhd::runtime_error( + "B100: timeout waiting for control response packet." + ); + + return pkt.data; +} + +/*********************************************************************** + * Viking marauders go pillaging for asynchronous control packets in the + * control response endpoint. Sync packets go in sync_ctrl_fifo, + * async TX error messages go in async_msg_fifo. sync_ctrl_fifo should + * never have more than 1 message in it, since it's expected that we'll + * wait for a control operation to finish before starting another one. + **********************************************************************/ +void b100_ctrl_impl::viking_marauder_loop(void){ + set_thread_priority_safe(); + + while (not boost::this_thread::interruption_requested()){ + managed_recv_buffer::sptr rbuf = _ctrl_transport->get_recv_buff(1.0); + if(rbuf.get() == NULL) continue; //that's ok, there are plenty of villages to pillage! + const boost::uint16_t *pkt_buf = rbuf->cast<const boost::uint16_t *>(); + + if(pkt_buf[0] >> 8 == CTRL_PACKET_HEADER_MAGIC) { + //so it's got a control packet header, let's parse it. + ctrl_pkt_t pkt; + unpack_ctrl_pkt(pkt_buf, pkt); + + if(pkt.pkt_meta.seq != boost::uint8_t(_seq - 1)) { + UHD_MSG(error) + << "Sequence error on control channel." << std::endl + << "Exiting control loop." << std::endl + ; + return; + } + if(pkt.pkt_meta.len > (CTRL_PACKET_LENGTH - CTRL_PACKET_HEADER_LENGTH)) { + UHD_MSG(error) + << "Control channel packet length too long" << std::endl + << "Exiting control loop." << std::endl + ; + return; + } + + //push it onto the queue + sync_ctrl_fifo.push_with_pop_on_full(pkt.data); + } + else{ //otherwise let the async callback handle it + boost::mutex::scoped_lock lock(_async_mutex); + if (not _async_cb.empty()) _async_cb(rbuf); + } + } +} + +bool b100_ctrl_impl::get_ctrl_data(ctrl_data_t &pkt_data, double timeout){ + boost::this_thread::disable_interruption di; //disable because the wait can throw + return sync_ctrl_fifo.pop_with_timed_wait(pkt_data, timeout); +} + +/*********************************************************************** + * Public make function for b100_ctrl interface + **********************************************************************/ +b100_ctrl::sptr b100_ctrl::make(uhd::transport::zero_copy_if::sptr ctrl_transport){ + return sptr(new b100_ctrl_impl(ctrl_transport)); +} diff --git a/host/lib/usrp/b100/b100_ctrl.hpp b/host/lib/usrp/b100/b100_ctrl.hpp new file mode 100644 index 000000000..74884d525 --- /dev/null +++ b/host/lib/usrp/b100/b100_ctrl.hpp @@ -0,0 +1,70 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CTRL_HPP +#define INCLUDED_B100_CTRL_HPP + +#include "wb_iface.hpp" +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include "ctrl_packet.hpp" +#include <boost/function.hpp> + +class b100_ctrl : boost::noncopyable, public wb_iface{ +public: + typedef boost::shared_ptr<b100_ctrl> sptr; + typedef boost::function<void(uhd::transport::managed_recv_buffer::sptr)> async_cb_type; + + /*! + * Make a USRP control object from a data transport + * \param ctrl_transport a USB data transport + * \return a new b100 control object + */ + static sptr make(uhd::transport::zero_copy_if::sptr ctrl_transport); + + //! set an async callback for messages + virtual void set_async_cb(const async_cb_type &async_cb) = 0; + + /*! + * Write a byte vector to an FPGA register + * \param addr the FPGA register address + * \param bytes the data to write + * \return 0 on success, error code on failure + */ + virtual int write(boost::uint32_t addr, const ctrl_data_t &data) = 0; + + /*! + * Read a byte vector from an FPGA register (blocking read) + * \param addr the FPGA register address + * \param len the length of the read + * \return a vector of bytes from the register(s) in question + */ + virtual ctrl_data_t read(boost::uint32_t addr, size_t len) = 0; + + /*! + * Get a sync ctrl packet (blocking) + * \param the packet data buffer + * \param the timeout value + * \return true if it got something + */ + virtual bool get_ctrl_data(ctrl_data_t &pkt_data, double timeout) = 0; + +}; + +#endif /* INCLUDED_B100_CTRL_HPP */ diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp new file mode 100644 index 000000000..6b888ef67 --- /dev/null +++ b/host/lib/usrp/b100/b100_impl.cpp @@ -0,0 +1,565 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "apply_corrections.hpp" +#include "b100_impl.hpp" +#include "b100_ctrl.hpp" +#include "fpga_regs_standard.h" +#include "usrp_i2c_addr.h" +#include "usrp_commands.h" +#include <uhd/transport/usb_control.hpp> +#include "ctrl_packet.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread/thread.hpp> +#include <boost/lexical_cast.hpp> +#include "b100_regs.hpp" +#include <cstdio> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +const boost::uint16_t B100_VENDOR_ID = 0x2500; +const boost::uint16_t B100_PRODUCT_ID = 0x0002; +const boost::uint16_t FX2_VENDOR_ID = 0x04b4; +const boost::uint16_t FX2_PRODUCT_ID = 0x8613; +static const boost::posix_time::milliseconds REENUMERATION_TIMEOUT_MS(3000); + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t b100_find(const device_addr_t &hint) +{ + device_addrs_t b100_addrs; + + //return an empty list of addresses when type is set to non-b100 + if (hint.has_key("type") and hint["type"] != "b100") return b100_addrs; + + //Return an empty list of addresses when an address is specified, + //since an address is intended for a different, non-USB, device. + if (hint.has_key("addr")) return b100_addrs; + + unsigned int vid, pid; + + if(hint.has_key("vid") && hint.has_key("pid") && hint.has_key("type") && hint["type"] == "b100") { + sscanf(hint.get("vid").c_str(), "%x", &vid); + sscanf(hint.get("pid").c_str(), "%x", &pid); + } else { + vid = B100_VENDOR_ID; + pid = B100_PRODUCT_ID; + } + + // Important note: + // The get device list calls are nested inside the for loop. + // This allows the usb guts to decontruct when not in use, + // so that re-enumeration after fw load can occur successfully. + // This requirement is a courtesy of libusb1.0 on windows. + + //find the usrps and load firmware + size_t found = 0; + BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { + //extract the firmware path for the b100 + std::string b100_fw_image; + try{ + b100_fw_image = find_image_path(hint.get("fw", B100_FW_FILE_NAME)); + } + catch(...){ + UHD_MSG(warning) << boost::format( + "Could not locate B100 firmware.\n" + "Please install the images package.\n" + ); + return b100_addrs; + } + UHD_LOG << "the firmware image: " << b100_fw_image << std::endl; + + usb_control::sptr control; + try{control = usb_control::make(handle, 0);} + catch(const uhd::exception &){continue;} //ignore claimed + + fx2_ctrl::make(control)->usrp_load_firmware(b100_fw_image); + found++; + } + + //get descriptors again with serial number, but using the initialized VID/PID now since we have firmware + vid = B100_VENDOR_ID; + pid = B100_PRODUCT_ID; + + const boost::system_time timeout_time = boost::get_system_time() + REENUMERATION_TIMEOUT_MS; + + //search for the device until found or timeout + while (boost::get_system_time() < timeout_time and b100_addrs.empty() and found != 0) + { + BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) + { + usb_control::sptr control; + try{control = usb_control::make(handle, 0);} + catch(const uhd::exception &){continue;} //ignore claimed + + fx2_ctrl::sptr fx2_ctrl = fx2_ctrl::make(control); + const mboard_eeprom_t mb_eeprom = mboard_eeprom_t(*fx2_ctrl, mboard_eeprom_t::MAP_B100); + device_addr_t new_addr; + new_addr["type"] = "b100"; + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = handle->get_serial(); + //this is a found b100 when the hint serial and name match or blank + if ( + (not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) + ){ + b100_addrs.push_back(new_addr); + } + } + } + + return b100_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr b100_make(const device_addr_t &device_addr){ + return device::sptr(new b100_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_b100_device){ + device::register_device(&b100_find, &b100_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +b100_impl::b100_impl(const device_addr_t &device_addr){ + size_t initialization_count = 0; + b100_impl_constructor_begin: + initialization_count++; + + _tree = property_tree::make(); + + //extract the FPGA path for the B100 + std::string b100_fpga_image = find_image_path( + device_addr.has_key("fpga")? device_addr["fpga"] : B100_FPGA_FILE_NAME + ); + + //try to match the given device address with something on the USB bus + std::vector<usb_device_handle::sptr> device_list = + usb_device_handle::get_device_list(B100_VENDOR_ID, B100_PRODUCT_ID); + + //locate the matching handle in the device list + usb_device_handle::sptr handle; + BOOST_FOREACH(usb_device_handle::sptr dev_handle, device_list) { + if (dev_handle->get_serial() == device_addr["serial"]){ + handle = dev_handle; + break; + } + } + UHD_ASSERT_THROW(handle.get() != NULL); //better be found + + //create control objects + usb_control::sptr fx2_transport = usb_control::make(handle, 0); + _fx2_ctrl = fx2_ctrl::make(fx2_transport); + this->check_fw_compat(); //check after making fx2 + //-- setup clock after making fx2 and before loading fpga --// + _clock_ctrl = b100_clock_ctrl::make(_fx2_ctrl, device_addr.cast<double>("master_clock_rate", B100_DEFAULT_TICK_RATE)); + + //load FPGA image, gpif is disabled while loading + this->enable_gpif(false); + _fx2_ctrl->usrp_load_fpga(b100_fpga_image); + _fx2_ctrl->usrp_fpga_reset(false); //active low reset + _fx2_ctrl->usrp_fpga_reset(true); + this->enable_gpif(true); + + //create the control transport + device_addr_t ctrl_xport_args; + ctrl_xport_args["recv_frame_size"] = boost::lexical_cast<std::string>(CTRL_PACKET_LENGTH); + ctrl_xport_args["num_recv_frames"] = "16"; + ctrl_xport_args["send_frame_size"] = boost::lexical_cast<std::string>(CTRL_PACKET_LENGTH); + ctrl_xport_args["num_send_frames"] = "4"; + + _ctrl_transport = usb_zero_copy::make( + handle, + 4, 8, //interface, endpoint + 3, 4, //interface, endpoint + ctrl_xport_args + ); + while (_ctrl_transport->get_recv_buff(0.0)){} //flush ctrl xport + + //////////////////////////////////////////////////////////////////// + // Initialize FPGA wishbone communication + //////////////////////////////////////////////////////////////////// + _fpga_ctrl = b100_ctrl::make(_ctrl_transport); + _fpga_ctrl->poke32(B100_REG_GLOBAL_RESET, 0); //global fpga reset + //perform a test peek operation + try{ + _fpga_ctrl->peek32(0); + } + //try reset once in the case of failure + catch(const uhd::exception &){ + if (initialization_count > 1) throw; + UHD_MSG(warning) << + "The control endpoint was left in a bad state.\n" + "Attempting endpoint re-enumeration...\n" << std::endl; + _fpga_ctrl.reset(); + _ctrl_transport.reset(); + _fx2_ctrl->usrp_fx2_reset(); + goto b100_impl_constructor_begin; + } + this->check_fpga_compat(); //check after reset and making control + + //////////////////////////////////////////////////////////////////// + // Initialize peripherals after reset + //////////////////////////////////////////////////////////////////// + _fpga_i2c_ctrl = i2c_core_100::make(_fpga_ctrl, B100_REG_SLAVE(3)); + _fpga_spi_ctrl = spi_core_100::make(_fpga_ctrl, B100_REG_SLAVE(2)); + + //////////////////////////////////////////////////////////////////// + // Create data transport + // This happens after FPGA ctrl instantiated so any junk that might + // be in the FPGAs buffers doesn't get pulled into the transport + // before being cleared. + //////////////////////////////////////////////////////////////////// + device_addr_t data_xport_args; + data_xport_args["recv_frame_size"] = device_addr.get("recv_frame_size", "16384"); + data_xport_args["num_recv_frames"] = device_addr.get("num_recv_frames", "16"); + data_xport_args["send_frame_size"] = device_addr.get("send_frame_size", "16384"); + data_xport_args["num_send_frames"] = device_addr.get("num_send_frames", "16"); + + _data_transport = usb_zero_copy::make_wrapper( + usb_zero_copy::make( + handle, // identifier + 2, 6, // IN interface, endpoint + 1, 2, // OUT interface, endpoint + data_xport_args // param hints + ), + B100_MAX_PKT_BYTE_LIMIT + ); + while (_data_transport->get_recv_buff(0.0)){} //flush data xport + + //////////////////////////////////////////////////////////////////// + // Initialize the properties tree + //////////////////////////////////////////////////////////////////// + _tree->create<std::string>("/name").set("B-Series Device"); + const fs_path mb_path = "/mboards/0"; + _tree->create<std::string>(mb_path / "name").set("B100 (B-Hundo)"); + _tree->create<std::string>(mb_path / "load_eeprom") + .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + + //////////////////////////////////////////////////////////////////// + // setup the mboard eeprom + //////////////////////////////////////////////////////////////////// + const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, mboard_eeprom_t::MAP_B100); + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(mb_eeprom) + .subscribe(boost::bind(&b100_impl::set_mb_eeprom, this, _1)); + + //////////////////////////////////////////////////////////////////// + // create clock control objects + //////////////////////////////////////////////////////////////////// + //^^^ clock created up top, just reg props here... ^^^ + _tree->create<double>(mb_path / "tick_rate") + .publish(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) + .subscribe(boost::bind(&b100_impl::update_tick_rate, this, _1)); + + //////////////////////////////////////////////////////////////////// + // create codec control objects + //////////////////////////////////////////////////////////////////// + _codec_ctrl = b100_codec_ctrl::make(_fpga_spi_ctrl); + const fs_path rx_codec_path = mb_path / "rx_codecs/A"; + const fs_path tx_codec_path = mb_path / "tx_codecs/A"; + _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); + _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(b100_codec_ctrl::rx_pga_gain_range); + _tree->create<double>(rx_codec_path / "gains/pga/value") + .coerce(boost::bind(&b100_impl::update_rx_codec_gain, this, _1)); + _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); + _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(b100_codec_ctrl::tx_pga_gain_range); + _tree->create<double>(tx_codec_path / "gains/pga/value") + .subscribe(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) + .publish(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); + + //////////////////////////////////////////////////////////////////// + // and do the misc mboard sensors + //////////////////////////////////////////////////////////////////// + _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") + .publish(boost::bind(&b100_impl::get_ref_locked, this)); + + //////////////////////////////////////////////////////////////////// + // create frontend control objects + //////////////////////////////////////////////////////////////////// + _rx_fe = rx_frontend_core_200::make(_fpga_ctrl, B100_REG_SR_ADDR(B100_SR_RX_FRONT)); + _tx_fe = tx_frontend_core_200::make(_fpga_ctrl, B100_REG_SR_ADDR(B100_SR_TX_FRONT)); + + _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") + .subscribe(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1)); + _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") + .subscribe(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1)); + + const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; + const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; + + _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") + .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) + .set(true); + _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") + .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) + .set(std::polar<double>(1.0, 0.0)); + _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") + .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) + .set(std::polar<double>(1.0, 0.0)); + + //////////////////////////////////////////////////////////////////// + // create rx dsp control objects + //////////////////////////////////////////////////////////////////// + _rx_dsps.push_back(rx_dsp_core_200::make( + _fpga_ctrl, B100_REG_SR_ADDR(B100_SR_RX_DSP0), B100_REG_SR_ADDR(B100_SR_RX_CTRL0), B100_RX_SID_BASE + 0 + )); + _rx_dsps.push_back(rx_dsp_core_200::make( + _fpga_ctrl, B100_REG_SR_ADDR(B100_SR_RX_DSP1), B100_REG_SR_ADDR(B100_SR_RX_CTRL1), B100_RX_SID_BASE + 1 + )); + for (size_t dspno = 0; dspno < _rx_dsps.size(); dspno++){ + _rx_dsps[dspno]->set_link_rate(B100_LINK_RATE_BPS); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); + fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); + _tree->create<meta_range_t>(rx_dsp_path / "rate/range") + .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); + _tree->create<double>(rx_dsp_path / "rate/value") + .set(1e6) //some default + .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) + .subscribe(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1)); + _tree->create<double>(rx_dsp_path / "freq/value") + .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); + _tree->create<meta_range_t>(rx_dsp_path / "freq/range") + .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); + } + + //////////////////////////////////////////////////////////////////// + // create tx dsp control objects + //////////////////////////////////////////////////////////////////// + _tx_dsp = tx_dsp_core_200::make( + _fpga_ctrl, B100_REG_SR_ADDR(B100_SR_TX_DSP), B100_REG_SR_ADDR(B100_SR_TX_CTRL), B100_TX_ASYNC_SID + ); + _tx_dsp->set_link_rate(B100_LINK_RATE_BPS); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); + _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") + .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); + _tree->create<double>(mb_path / "tx_dsps/0/rate/value") + .set(1e6) //some default + .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) + .subscribe(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1)); + _tree->create<double>(mb_path / "tx_dsps/0/freq/value") + .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); + _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") + .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + + //////////////////////////////////////////////////////////////////// + // create time control objects + //////////////////////////////////////////////////////////////////// + time64_core_200::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_hi_now = B100_REG_RB_TIME_NOW_HI; + time64_rb_bases.rb_lo_now = B100_REG_RB_TIME_NOW_LO; + time64_rb_bases.rb_hi_pps = B100_REG_RB_TIME_PPS_HI; + time64_rb_bases.rb_lo_pps = B100_REG_RB_TIME_PPS_LO; + _time64 = time64_core_200::make( + _fpga_ctrl, B100_REG_SR_ADDR(B100_SR_TIME64), time64_rb_bases + ); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); + _tree->create<time_spec_t>(mb_path / "time/now") + .publish(boost::bind(&time64_core_200::get_time_now, _time64)) + .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); + _tree->create<time_spec_t>(mb_path / "time/pps") + .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) + .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); + //setup time source props + _tree->create<std::string>(mb_path / "time_source/value") + .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); + _tree->create<std::vector<std::string> >(mb_path / "time_source/options") + .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); + //setup reference source props + _tree->create<std::string>(mb_path / "clock_source/value") + .subscribe(boost::bind(&b100_impl::update_clock_source, this, _1)); + static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); + _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); + + //////////////////////////////////////////////////////////////////// + // create user-defined control objects + //////////////////////////////////////////////////////////////////// + _user = user_settings_core_200::make(_fpga_ctrl, B100_REG_SR_ADDR(B100_SR_USER_REGS)); + _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") + .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + + //////////////////////////////////////////////////////////////////// + // create dboard control objects + //////////////////////////////////////////////////////////////////// + + //read the dboard eeprom to extract the dboard ids + dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; + rx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_RX_A); + tx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_A); + gdb_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_A ^ 5); + + //create the properties and register subscribers + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") + .set(rx_db_eeprom) + .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1)); + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") + .set(tx_db_eeprom) + .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1)); + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") + .set(gdb_eeprom) + .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1)); + + //create a new dboard interface and manager + _dboard_iface = make_b100_dboard_iface(_fpga_ctrl, _fpga_i2c_ctrl, _fpga_spi_ctrl, _clock_ctrl, _codec_ctrl); + _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); + _dboard_manager = dboard_manager::make( + rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, + _dboard_iface, _tree->subtree(mb_path / "dboards/A") + ); + + //bind frontend corrections to the dboard freq props + const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ + _tree->access<double>(db_tx_fe_path / name / "freq" / "value") + .subscribe(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1)); + } + const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ + _tree->access<double>(db_rx_fe_path / name / "freq" / "value") + .subscribe(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1)); + } + + //initialize io handling + this->io_init(); + + //////////////////////////////////////////////////////////////////// + // do some post-init tasks + //////////////////////////////////////////////////////////////////// + this->update_rates(); + + _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter + .subscribe(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + + //reset cordic rates and their properties to zero + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ + _tree->access<double>(mb_path / "rx_dsps" / name / "freq" / "value").set(0.0); + } + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ + _tree->access<double>(mb_path / "tx_dsps" / name / "freq" / "value").set(0.0); + } + + _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(mb_path / "dboards/A/rx_frontends").at(0))); + _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(mb_path / "dboards/A/tx_frontends").at(0))); + _tree->access<std::string>(mb_path / "clock_source/value").set("internal"); + _tree->access<std::string>(mb_path / "time_source/value").set("none"); +} + +b100_impl::~b100_impl(void){ + //set an empty async callback now that we deconstruct + _fpga_ctrl->set_async_cb(b100_ctrl::async_cb_type()); +} + +void b100_impl::check_fw_compat(void){ + unsigned char data[4]; //useless data buffer + const boost::uint16_t fw_compat_num = _fx2_ctrl->usrp_control_read( + VRQ_FW_COMPAT, 0, 0, data, sizeof(data) + ); + if (fw_compat_num != B100_FW_COMPAT_NUM){ + throw uhd::runtime_error(str(boost::format( + "Expected firmware compatibility number 0x%x, but got 0x%x:\n" + "The firmware build is not compatible with the host code build." + ) % B100_FW_COMPAT_NUM % fw_compat_num)); + } +} + +void b100_impl::check_fpga_compat(void){ + const boost::uint32_t fpga_compat_num = _fpga_ctrl->peek32(B100_REG_RB_COMPAT); + boost::uint16_t fpga_major = fpga_compat_num >> 16, fpga_minor = fpga_compat_num & 0xffff; + if (fpga_major == 0){ //old version scheme + fpga_major = fpga_minor; + fpga_minor = 0; + } + if (fpga_major != B100_FPGA_COMPAT_NUM){ + throw uhd::runtime_error(str(boost::format( + "Expected FPGA compatibility number %d, but got %d:\n" + "The FPGA build is not compatible with the host code build." + ) % int(B100_FPGA_COMPAT_NUM) % fpga_major)); + } + _tree->create<std::string>("/mboards/0/fpga_version").set(str(boost::format("%u.%u") % fpga_major % fpga_minor)); +} + +double b100_impl::update_rx_codec_gain(const double gain){ + //set gain on both I and Q, readback on one + //TODO in the future, gains should have individual control + _codec_ctrl->set_rx_pga_gain(gain, 'A'); + _codec_ctrl->set_rx_pga_gain(gain, 'B'); + return _codec_ctrl->get_rx_pga_gain('A'); +} + +void b100_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom){ + mb_eeprom.commit(*_fx2_ctrl, mboard_eeprom_t::MAP_B100); +} + +void b100_impl::set_db_eeprom(const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ + if (type == "rx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_RX_A); + if (type == "tx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_A); + if (type == "gdb") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_A ^ 5); +} + +void b100_impl::update_clock_source(const std::string &source){ + if (source == "auto") _clock_ctrl->use_auto_ref(); + else if (source == "internal") _clock_ctrl->use_internal_ref(); + else if (source == "external") _clock_ctrl->use_external_ref(); + else throw uhd::runtime_error("unhandled clock configuration reference source: " + source); +} + +////////////////// some GPIF preparation related stuff ///////////////// +void b100_impl::enable_gpif(const bool en) { + _fx2_ctrl->usrp_control_write(VRQ_ENABLE_GPIF, en ? 1 : 0, 0, 0, 0); +} + +void b100_impl::clear_fpga_fifo(void) { + _fx2_ctrl->usrp_control_write(VRQ_CLEAR_FPGA_FIFO, 0, 0, 0, 0); +} + +sensor_value_t b100_impl::get_ref_locked(void){ + const bool lock = _clock_ctrl->get_locked(); + return sensor_value_t("Ref", lock, "locked", "unlocked"); +} + +void b100_impl::set_rx_fe_corrections(const double lo_freq){ + apply_rx_fe_corrections(this->get_tree()->subtree("/mboards/0"), "A", lo_freq); +} + +void b100_impl::set_tx_fe_corrections(const double lo_freq){ + apply_tx_fe_corrections(this->get_tree()->subtree("/mboards/0"), "A", lo_freq); +} diff --git a/host/lib/usrp/b100/b100_impl.hpp b/host/lib/usrp/b100/b100_impl.hpp new file mode 100644 index 000000000..eab9c750b --- /dev/null +++ b/host/lib/usrp/b100/b100_impl.hpp @@ -0,0 +1,134 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_IMPL_HPP +#define INCLUDED_B100_IMPL_HPP + +#include "fx2_ctrl.hpp" +#include "b100_ctrl.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "spi_core_100.hpp" +#include "i2c_core_100.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "rx_dsp_core_200.hpp" +#include "tx_dsp_core_200.hpp" +#include "time64_core_200.hpp" +#include "user_settings_core_200.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <boost/weak_ptr.hpp> + +static const double B100_LINK_RATE_BPS = 256e6/5; //pratical link rate (< 480 Mbps) +static const std::string B100_FW_FILE_NAME = "usrp_b100_fw.ihx"; +static const std::string B100_FPGA_FILE_NAME = "usrp_b100_fpga.bin"; +static const boost::uint16_t B100_FW_COMPAT_NUM = 0x03; +static const boost::uint16_t B100_FPGA_COMPAT_NUM = 0x09; +static const boost::uint32_t B100_RX_SID_BASE = 2; +static const boost::uint32_t B100_TX_ASYNC_SID = 1; +static const double B100_DEFAULT_TICK_RATE = 64e6; +static const size_t B100_MAX_PKT_BYTE_LIMIT = 2048; + +//! Make a b100 dboard interface +uhd::usrp::dboard_iface::sptr make_b100_dboard_iface( + wb_iface::sptr wb_iface, + uhd::i2c_iface::sptr i2c_iface, + uhd::spi_iface::sptr spi_iface, + b100_clock_ctrl::sptr clock, + b100_codec_ctrl::sptr codec +); + +//! Implementation guts +class b100_impl : public uhd::device { +public: + //structors + b100_impl(const uhd::device_addr_t &); + ~b100_impl(void); + + //the io interface + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + bool recv_async_msg(uhd::async_metadata_t &, double); + +private: + uhd::property_tree::sptr _tree; + + //controllers + spi_core_100::sptr _fpga_spi_ctrl; + i2c_core_100::sptr _fpga_i2c_ctrl; + rx_frontend_core_200::sptr _rx_fe; + tx_frontend_core_200::sptr _tx_fe; + std::vector<rx_dsp_core_200::sptr> _rx_dsps; + tx_dsp_core_200::sptr _tx_dsp; + time64_core_200::sptr _time64; + user_settings_core_200::sptr _user; + b100_clock_ctrl::sptr _clock_ctrl; + b100_codec_ctrl::sptr _codec_ctrl; + b100_ctrl::sptr _fpga_ctrl; + uhd::usrp::fx2_ctrl::sptr _fx2_ctrl; + + //transports + uhd::transport::zero_copy_if::sptr _data_transport, _ctrl_transport; + + //dboard stuff + uhd::usrp::dboard_manager::sptr _dboard_manager; + uhd::usrp::dboard_iface::sptr _dboard_iface; + + //handle io stuff + UHD_PIMPL_DECL(io_impl) _io_impl; + void io_init(void); + + //device properties interface + uhd::property_tree::sptr get_tree(void) const{ + return _tree; + } + + std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; + std::vector<boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; + + void check_fw_compat(void); + void check_fpga_compat(void); + double update_rx_codec_gain(const double); //sets A and B at once + void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); + void set_db_eeprom(const std::string &, const uhd::usrp::dboard_eeprom_t &); + void update_tick_rate(const double rate); + void update_rx_samp_rate(const size_t, const double rate); + void update_tx_samp_rate(const size_t, const double rate); + void update_rates(void); + void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void update_clock_source(const std::string &); + void enable_gpif(const bool); + void clear_fpga_fifo(void); + void handle_async_message(uhd::transport::managed_recv_buffer::sptr); + uhd::sensor_value_t get_ref_locked(void); + void set_rx_fe_corrections(const double); + void set_tx_fe_corrections(const double); +}; + +#endif /* INCLUDED_b100_IMPL_HPP */ diff --git a/host/lib/usrp/b100/b100_regs.hpp b/host/lib/usrp/b100/b100_regs.hpp new file mode 100644 index 000000000..987a09f03 --- /dev/null +++ b/host/lib/usrp/b100/b100_regs.hpp @@ -0,0 +1,123 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +//////////////////////////////////////////////////////////////// +// +// Memory map for wishbone bus +// +//////////////////////////////////////////////////////////////// + +// All addresses are byte addresses. All accesses are word (16-bit) accesses. +// This means that address bit 0 is usually 0. +// There are 11 bits of address for the control. + +#ifndef INCLUDED_B100_REGS_HPP +#define INCLUDED_B100_REGS_HPP + +///////////////////////////////////////////////////// +// Slave pointers + +#define B100_REG_SLAVE(n) ((n)<<7) + +///////////////////////////////////////////////////// +// Slave 0 -- Misc Regs + +#define B100_REG_MISC_BASE B100_REG_SLAVE(0) + +#define B100_REG_MISC_LED B100_REG_MISC_BASE + 0 +#define B100_REG_MISC_SW B100_REG_MISC_BASE + 2 +#define B100_REG_MISC_CGEN_CTRL B100_REG_MISC_BASE + 4 +#define B100_REG_MISC_CGEN_ST B100_REG_MISC_BASE + 6 + +///////////////////////////////////////////////////// +// Slave 1 -- UART +// CLKDIV is 16 bits, others are only 8 + +#define B100_REG_UART_BASE B100_REG_SLAVE(1) + +#define B100_REG_UART_CLKDIV B100_REG_UART_BASE + 0 +#define B100_REG_UART_TXLEVEL B100_REG_UART_BASE + 2 +#define B100_REG_UART_RXLEVEL B100_REG_UART_BASE + 4 +#define B100_REG_UART_TXCHAR B100_REG_UART_BASE + 6 +#define B100_REG_UART_RXCHAR B100_REG_UART_BASE + 8 + +///////////////////////////////////////////////////// +// Slave 2 -- SPI Core +//these are 32-bit registers mapped onto the 16-bit Wishbone bus. +//Using peek32/poke32 should allow transparent use of these registers. +#define B100_REG_SPI_BASE B100_REG_SLAVE(2) + +//spi slave constants +#define B100_SPI_SS_AD9862 (1 << 2) +#define B100_SPI_SS_TX_DB (1 << 1) +#define B100_SPI_SS_RX_DB (1 << 0) + +//////////////////////////////////////////////// +// Slave 3 -- I2C Core + +#define B100_REG_I2C_BASE B100_REG_SLAVE(3) + +/////////////////////////////////////////////////// +// Slave 7 -- Readback Mux 32 + +#define B100_REG_RB_MUX_32_BASE B100_REG_SLAVE(7) + +#define B100_REG_RB_TIME_NOW_HI B100_REG_RB_MUX_32_BASE + 0 +#define B100_REG_RB_TIME_NOW_LO B100_REG_RB_MUX_32_BASE + 4 +#define B100_REG_RB_TIME_PPS_HI B100_REG_RB_MUX_32_BASE + 8 +#define B100_REG_RB_TIME_PPS_LO B100_REG_RB_MUX_32_BASE + 12 +#define B100_REG_RB_MISC_TEST32 B100_REG_RB_MUX_32_BASE + 16 +#define B100_REG_RB_COMPAT B100_REG_RB_MUX_32_BASE + 24 +#define B100_REG_RB_GPIO B100_REG_RB_MUX_32_BASE + 28 + +//////////////////////////////////////////////////// +// Slaves 8 & 9 -- Settings Bus +// +// Output-only, no readback, 64 registers total +// Each register must be written 32 bits at a time +// First the address xxx_xx00 and then xxx_xx10 +// 64 total regs in address space +#define B100_SR_RX_CTRL0 0 // 9 regs (+0 to +8) +#define B100_SR_RX_DSP0 10 // 4 regs (+0 to +3) +#define B100_SR_RX_CTRL1 16 // 9 regs (+0 to +8) +#define B100_SR_RX_DSP1 26 // 4 regs (+0 to +3) +#define B100_SR_TX_CTRL 32 // 4 regs (+0 to +3) +#define B100_SR_TX_DSP 38 // 3 regs (+0 to +2) + +#define B100_SR_TIME64 42 // 6 regs (+0 to +5) +#define B100_SR_RX_FRONT 48 // 5 regs (+0 to +4) +#define B100_SR_TX_FRONT 54 // 5 regs (+0 to +4) + +#define B100_SR_REG_TEST32 60 // 1 reg +#define B100_SR_CLEAR_FIFO 61 // 1 reg +#define B100_SR_GLOBAL_RESET 63 // 1 reg +#define B100_SR_USER_REGS 64 // 2 regs + +#define B100_SR_GPIO 128 + +#define B100_REG_SR_ADDR(n) (B100_REG_SLAVE(8) + (4*(n))) + +#define B100_REG_SR_MISC_TEST32 B100_REG_SR_ADDR(B100_SR_REG_TEST32) + +///////////////////////////////////////////////// +// Magic reset regs +//////////////////////////////////////////////// +#define B100_REG_CLEAR_FIFO B100_REG_SR_ADDR(B100_SR_CLEAR_FIFO) +#define B100_REG_GLOBAL_RESET B100_REG_SR_ADDR(B100_SR_GLOBAL_RESET) + +#endif + diff --git a/host/lib/usrp/b100/clock_ctrl.cpp b/host/lib/usrp/b100/clock_ctrl.cpp new file mode 100644 index 000000000..cbe6c40a0 --- /dev/null +++ b/host/lib/usrp/b100/clock_ctrl.cpp @@ -0,0 +1,536 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9522_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include "b100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <algorithm> +#include <utility> + +using namespace uhd; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const bool ENABLE_THE_TEST_OUT = true; +static const double REFERENCE_INPUT_RATE = 10e6; + +/*********************************************************************** + * Helpers + **********************************************************************/ +template <typename div_type, typename bypass_type> static void set_clock_divider( + size_t divider, div_type &low, div_type &high, bypass_type &bypass +){ + high = divider/2 - 1; + low = divider - high - 2; + bypass = (divider == 1)? 1 : 0; +} + +/*********************************************************************** + * Clock rate calculation stuff: + * Using the internal VCO between 1400 and 1800 MHz + **********************************************************************/ +struct clock_settings_type{ + size_t ref_clock_doubler, r_counter, a_counter, b_counter, prescaler, vco_divider, chan_divider; + size_t get_n_counter(void) const{return prescaler * b_counter + a_counter;} + double get_ref_rate(void) const{return REFERENCE_INPUT_RATE * ref_clock_doubler;} + double get_vco_rate(void) const{return get_ref_rate()/r_counter * get_n_counter();} + double get_chan_rate(void) const{return get_vco_rate()/vco_divider;} + double get_out_rate(void) const{return get_chan_rate()/chan_divider;} + std::string to_pp_string(void) const{ + return str(boost::format( + " r_counter: %d\n" + " a_counter: %d\n" + " b_counter: %d\n" + " prescaler: %d\n" + " vco_divider: %d\n" + " chan_divider: %d\n" + " vco_rate: %fMHz\n" + " chan_rate: %fMHz\n" + " out_rate: %fMHz\n" + ) + % r_counter + % a_counter + % b_counter + % prescaler + % vco_divider + % chan_divider + % (get_vco_rate()/1e6) + % (get_chan_rate()/1e6) + % (get_out_rate()/1e6) + ); + } +}; + +//! gives the greatest divisor of num between 1 and max inclusive +template<typename T> static inline T greatest_divisor(T num, T max){ + for (T i = max; i > 1; i--) if (num%i == 0) return i; return 1; +} + +//! gives the least divisor of num between min and num exclusive +template<typename T> static inline T least_divisor(T num, T min){ + for (T i = min; i < num; i++) if (num%i == 0) return i; return 1; +} + +static clock_settings_type get_clock_settings(double rate){ + clock_settings_type cs; + cs.ref_clock_doubler = 2; //always doubling + cs.prescaler = 8; //set to 8 when input is under 2400 MHz + + //basic formulas used below: + //out_rate*X = ref_rate*Y + //X = i*ref_rate/gcd + //Y = i*out_rate/gcd + //X = chan_div * vco_div * R + //Y = P*B + A + + const boost::uint64_t out_rate = boost::uint64_t(rate); + const boost::uint64_t ref_rate = boost::uint64_t(cs.get_ref_rate()); + const size_t gcd = size_t(boost::math::gcd(ref_rate, out_rate)); + + for (size_t i = 1; i <= 100; i++){ + const size_t X = i*ref_rate/gcd; + const size_t Y = i*out_rate/gcd; + + //determine A and B (P is fixed) + cs.b_counter = Y/cs.prescaler; + cs.a_counter = Y - cs.b_counter*cs.prescaler; + + static const double vco_bound_pad = 100e6; + for ( //calculate an r divider that fits into the bounds of the vco + cs.r_counter = size_t(cs.get_n_counter()*cs.get_ref_rate()/(1800e6 - vco_bound_pad)); + cs.r_counter <= size_t(cs.get_n_counter()*cs.get_ref_rate()/(1400e6 + vco_bound_pad)) + and cs.r_counter > 0; cs.r_counter++ + ){ + + //determine chan_div and vco_div + //and fill in that order of preference + cs.chan_divider = greatest_divisor<size_t>(X/cs.r_counter, 32); + cs.vco_divider = greatest_divisor<size_t>(X/cs.chan_divider/cs.r_counter, 6); + + //avoid a vco divider of 1 (if possible) + if (cs.vco_divider == 1){ + cs.vco_divider = least_divisor<size_t>(cs.chan_divider, 2); + cs.chan_divider /= cs.vco_divider; + } + + UHD_LOGV(always) + << "gcd " << gcd << std::endl + << "X " << X << std::endl + << "Y " << Y << std::endl + << cs.to_pp_string() << std::endl + ; + + //filter limits on the counters + if (cs.vco_divider == 1) continue; + if (cs.r_counter >= (1<<14)) continue; + if (cs.b_counter == 2) continue; + if (cs.b_counter == 1 and cs.a_counter != 0) continue; + if (cs.b_counter >= (1<<13)) continue; + if (cs.a_counter >= (1<<6)) continue; + if (cs.get_vco_rate() > 1800e6 - vco_bound_pad) continue; + if (cs.get_vco_rate() < 1400e6 + vco_bound_pad) continue; + if (cs.get_out_rate() != rate) continue; + + UHD_MSG(status) << "USRP-B100 clock control: " << i << std::endl << cs.to_pp_string() << std::endl; + return cs; + } + } + + throw uhd::value_error(str(boost::format( + "USRP-B100 clock control: could not calculate settings for clock rate %fMHz" + ) % (rate/1e6))); +} + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class b100_clock_ctrl_impl : public b100_clock_ctrl{ +public: + b100_clock_ctrl_impl(i2c_iface::sptr iface, double master_clock_rate){ + _iface = iface; + _chan_rate = 0.0; + _out_rate = 0.0; + + //perform soft-reset + _ad9522_regs.soft_reset = 1; + this->send_reg(0x000); + this->latch_regs(); + _ad9522_regs.soft_reset = 0; + + //init the clock gen registers + _ad9522_regs.sdo_active = ad9522_regs_t::SDO_ACTIVE_SDO_SDIO; + _ad9522_regs.enb_stat_eeprom_at_stat_pin = 0; //use status pin + _ad9522_regs.status_pin_control = 0x1; //n divider + _ad9522_regs.ld_pin_control = 0x00; //dld + _ad9522_regs.refmon_pin_control = 0x12; //show ref2 + _ad9522_regs.lock_detect_counter = ad9522_regs_t::LOCK_DETECT_COUNTER_16CYC; + + this->use_internal_ref(); + + this->set_fpga_clock_rate(master_clock_rate); //initialize to something + + this->enable_fpga_clock(true); + this->enable_test_clock(ENABLE_THE_TEST_OUT); + this->enable_rx_dboard_clock(false); + this->enable_tx_dboard_clock(false); + } + + ~b100_clock_ctrl_impl(void){ + UHD_SAFE_CALL( + this->enable_test_clock(ENABLE_THE_TEST_OUT); + this->enable_rx_dboard_clock(false); + this->enable_tx_dboard_clock(false); + //this->enable_fpga_clock(false); //FIXME + ) + } + + /*********************************************************************** + * Clock rate control: + * - set clock rate w/ internal VCO + * - set clock rate w/ external VCXO + **********************************************************************/ + void set_clock_settings_with_internal_vco(double rate){ + const clock_settings_type cs = get_clock_settings(rate); + + //set the rates to private variables so the implementation knows! + _chan_rate = cs.get_chan_rate(); + _out_rate = cs.get_out_rate(); + + _ad9522_regs.enable_clock_doubler = (cs.ref_clock_doubler == 2)? 1 : 0; + + _ad9522_regs.set_r_counter(cs.r_counter); + _ad9522_regs.a_counter = cs.a_counter; + _ad9522_regs.set_b_counter(cs.b_counter); + UHD_ASSERT_THROW(cs.prescaler == 8); //assumes this below: + _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV8_9; + + _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; + _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + + _ad9522_regs.bypass_vco_divider = 0; + switch(cs.vco_divider){ + case 1: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV1; break; + case 2: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV2; break; + case 3: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV3; break; + case 4: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV4; break; + case 5: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV5; break; + case 6: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV6; break; + } + _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_VCO; + + //setup fpga master clock + _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; + set_clock_divider(cs.chan_divider, + _ad9522_regs.divider0_low_cycles, + _ad9522_regs.divider0_high_cycles, + _ad9522_regs.divider0_bypass + ); + + //setup codec clock + _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; + set_clock_divider(cs.chan_divider, + _ad9522_regs.divider1_low_cycles, + _ad9522_regs.divider1_high_cycles, + _ad9522_regs.divider1_bypass + ); + + this->send_all_regs(); + calibrate_now(); + } + + void set_clock_settings_with_external_vcxo(double rate){ + //set the rates to private variables so the implementation knows! + _chan_rate = rate; + _out_rate = rate; + + _ad9522_regs.enable_clock_doubler = 1; //doubler always on + const double ref_rate = REFERENCE_INPUT_RATE*2; + + //bypass prescaler such that N = B + long gcd = boost::math::gcd(long(ref_rate), long(rate)); + _ad9522_regs.set_r_counter(int(ref_rate/gcd)); + _ad9522_regs.a_counter = 0; + _ad9522_regs.set_b_counter(int(rate/gcd)); + _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV1; + + //setup external vcxo + _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; + _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + _ad9522_regs.bypass_vco_divider = 1; + _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_EXTERNAL; + + //setup fpga master clock + _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; + _ad9522_regs.divider0_bypass = 1; + + //setup codec clock + _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; + _ad9522_regs.divider1_bypass = 1; + + this->send_all_regs(); + } + + void set_fpga_clock_rate(double rate){ + if (_out_rate == rate) return; + if (rate == 61.44e6) set_clock_settings_with_external_vcxo(rate); + else set_clock_settings_with_internal_vco(rate); + //clock rate changed! update dboard clocks and FPGA ticks per second + set_rx_dboard_clock_rate(rate); + set_tx_dboard_clock_rate(rate); + } + + double get_fpga_clock_rate(void){ + return this->_out_rate; + } + + /*********************************************************************** + * FPGA clock enable + **********************************************************************/ + void enable_fpga_clock(bool enb){ + _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; + _ad9522_regs.out0_lvds_power_down = !enb; + this->send_reg(0x0F0); + this->latch_regs(); + } + + /*********************************************************************** + * Special test clock output + **********************************************************************/ + void enable_test_clock(bool enb){ + //setup test clock (same divider as codec clock) + _ad9522_regs.out4_format = ad9522_regs_t::OUT4_FORMAT_CMOS; + _ad9522_regs.out4_cmos_configuration = (enb)? + ad9522_regs_t::OUT4_CMOS_CONFIGURATION_A_ON : + ad9522_regs_t::OUT4_CMOS_CONFIGURATION_OFF; + this->send_reg(0x0F4); + this->latch_regs(); + } + + /*********************************************************************** + * RX Dboard Clock Control (output 9, divider 3) + **********************************************************************/ + void enable_rx_dboard_clock(bool enb){ + _ad9522_regs.out9_format = ad9522_regs_t::OUT9_FORMAT_LVDS; + _ad9522_regs.out9_lvds_power_down = !enb; + this->send_reg(0x0F9); + this->latch_regs(); + } + + std::vector<double> get_rx_dboard_clock_rates(void){ + std::vector<double> rates; + for(size_t div = 1; div <= 16+16; div++) + rates.push_back(this->_chan_rate/div); + return rates; + } + + void set_rx_dboard_clock_rate(double rate){ + assert_has(get_rx_dboard_clock_rates(), rate, "rx dboard clock rate"); + _rx_clock_rate = rate; + size_t divider = size_t(this->_chan_rate/rate); + //set the divider registers + set_clock_divider(divider, + _ad9522_regs.divider3_low_cycles, + _ad9522_regs.divider3_high_cycles, + _ad9522_regs.divider3_bypass + ); + this->send_reg(0x199); + this->send_reg(0x19a); + this->soft_sync(); + } + + double get_rx_clock_rate(void){ + return _rx_clock_rate; + } + + /*********************************************************************** + * TX Dboard Clock Control (output 6, divider 2) + **********************************************************************/ + void enable_tx_dboard_clock(bool enb){ + _ad9522_regs.out6_format = ad9522_regs_t::OUT6_FORMAT_LVDS; + _ad9522_regs.out6_lvds_power_down = !enb; + this->send_reg(0x0F6); + this->latch_regs(); + } + + std::vector<double> get_tx_dboard_clock_rates(void){ + return get_rx_dboard_clock_rates(); //same master clock, same dividers... + } + + void set_tx_dboard_clock_rate(double rate){ + assert_has(get_tx_dboard_clock_rates(), rate, "tx dboard clock rate"); + _tx_clock_rate = rate; + size_t divider = size_t(this->_chan_rate/rate); + //set the divider registers + set_clock_divider(divider, + _ad9522_regs.divider2_low_cycles, + _ad9522_regs.divider2_high_cycles, + _ad9522_regs.divider2_bypass + ); + this->send_reg(0x196); + this->send_reg(0x197); + this->soft_sync(); + } + + double get_tx_clock_rate(void){ + return _tx_clock_rate; + } + + /*********************************************************************** + * Clock reference control + **********************************************************************/ + void use_internal_ref(void) { + _ad9522_regs.enable_ref2 = 1; + _ad9522_regs.enable_ref1 = 0; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF2; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; + this->send_reg(0x01C); + this->latch_regs(); + } + + void use_external_ref(void) { + _ad9522_regs.enable_ref2 = 0; + _ad9522_regs.enable_ref1 = 1; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; + this->send_reg(0x01C); + this->latch_regs(); + } + + void use_auto_ref(void) { + _ad9522_regs.enable_ref2 = 1; + _ad9522_regs.enable_ref1 = 1; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_AUTO; + this->send_reg(0x01C); + this->latch_regs(); + } + + bool get_locked(void){ + static const boost::uint8_t addr = 0x01F; + boost::uint32_t reg = this->read_reg(addr); + _ad9522_regs.set_reg(addr, reg); + return _ad9522_regs.digital_lock_detect != 0; + } + +private: + i2c_iface::sptr _iface; + ad9522_regs_t _ad9522_regs; + double _out_rate; //rate at the fpga and codec + double _chan_rate; //rate before final dividers + double _rx_clock_rate, _tx_clock_rate; + + void latch_regs(void){ + _ad9522_regs.io_update = 1; + this->send_reg(0x232); + } + + void send_reg(boost::uint16_t addr){ + boost::uint32_t reg = _ad9522_regs.get_write_reg(addr); + UHD_LOGV(often) << "clock control write reg: " << std::hex << reg << std::endl; + byte_vector_t buf; + buf.push_back(boost::uint8_t(reg >> 16)); + buf.push_back(boost::uint8_t(reg >> 8)); + buf.push_back(boost::uint8_t(reg & 0xff)); + + _iface->write_i2c(0x5C, buf); + } + + boost::uint8_t read_reg(boost::uint16_t addr){ + byte_vector_t buf; + buf.push_back(boost::uint8_t(addr >> 8)); + buf.push_back(boost::uint8_t(addr & 0xff)); + _iface->write_i2c(0x5C, buf); + + buf = _iface->read_i2c(0x5C, 1); + + return boost::uint32_t(buf[0] & 0xFF); + } + + void calibrate_now(void){ + //vco calibration routine: + _ad9522_regs.vco_calibration_now = 0; + this->send_reg(0x18); + this->latch_regs(); + _ad9522_regs.vco_calibration_now = 1; + this->send_reg(0x18); + this->latch_regs(); + //wait for calibration done: + static const boost::uint8_t addr = 0x01F; + for (size_t ms10 = 0; ms10 < 100; ms10++){ + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + boost::uint32_t reg = read_reg(addr); + _ad9522_regs.set_reg(addr, reg); + if (_ad9522_regs.vco_calibration_finished) goto wait_for_ld; + } + UHD_MSG(error) << "USRP-B100 clock control: VCO calibration timeout" << std::endl; + wait_for_ld: + //wait for digital lock detect: + for (size_t ms10 = 0; ms10 < 100; ms10++){ + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + boost::uint32_t reg = read_reg(addr); + _ad9522_regs.set_reg(addr, reg); + if (_ad9522_regs.digital_lock_detect) return; + } + UHD_MSG(error) << "USRP-B100 clock control: lock detection timeout" << std::endl; + } + + void soft_sync(void){ + _ad9522_regs.soft_sync = 1; + this->send_reg(0x230); + this->latch_regs(); + _ad9522_regs.soft_sync = 0; + this->send_reg(0x230); + this->latch_regs(); + } + + void send_all_regs(void){ + //setup a list of register ranges to write + typedef std::pair<boost::uint16_t, boost::uint16_t> range_t; + static const std::vector<range_t> ranges = boost::assign::list_of + (range_t(0x000, 0x000)) (range_t(0x010, 0x01F)) + (range_t(0x0F0, 0x0FD)) (range_t(0x190, 0x19B)) + (range_t(0x1E0, 0x1E1)) (range_t(0x230, 0x230)) + ; + + //write initial register values and latch/update + BOOST_FOREACH(const range_t &range, ranges){ + for(boost::uint16_t addr = range.first; addr <= range.second; addr++){ + this->send_reg(addr); + } + } + this->latch_regs(); + } +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +b100_clock_ctrl::sptr b100_clock_ctrl::make(i2c_iface::sptr iface, double master_clock_rate){ + return sptr(new b100_clock_ctrl_impl(iface, master_clock_rate)); +} diff --git a/host/lib/usrp/b100/clock_ctrl.hpp b/host/lib/usrp/b100/clock_ctrl.hpp new file mode 100644 index 000000000..387892bf7 --- /dev/null +++ b/host/lib/usrp/b100/clock_ctrl.hpp @@ -0,0 +1,133 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CLOCK_CTRL_HPP +#define INCLUDED_B100_CLOCK_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp-e clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class b100_clock_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<b100_clock_ctrl> sptr; + + /*! + * Make a new clock control object. + * \param iface the controller iface object + * \param master_clock_rate the master FPGA/sample clock rate + * \return the clock control object + */ + static sptr make(uhd::i2c_iface::sptr iface, double master_clock_rate); + + /*! + * Set the rate of the fpga clock line. + * Throws if rate is not valid. + * \param rate the new rate in Hz + */ + virtual void set_fpga_clock_rate(double rate) = 0; + + /*! + * Get the rate of the fpga clock line. + * \return the fpga clock rate in Hz + */ + virtual double get_fpga_clock_rate(void) = 0; + + /*! + * Get the possible rates of the rx dboard clock. + * \return a vector of clock rates in Hz + */ + virtual std::vector<double> get_rx_dboard_clock_rates(void) = 0; + + /*! + * Get the possible rates of the tx dboard clock. + * \return a vector of clock rates in Hz + */ + virtual std::vector<double> get_tx_dboard_clock_rates(void) = 0; + + /*! + * Set the rx dboard clock rate to a possible rate. + * \param rate the new clock rate in Hz + * \throw exception when rate cannot be achieved + */ + virtual void set_rx_dboard_clock_rate(double rate) = 0; + + /*! + * Set the tx dboard clock rate to a possible rate. + * \param rate the new clock rate in Hz + * \throw exception when rate cannot be achieved + */ + virtual void set_tx_dboard_clock_rate(double rate) = 0; + + /*! + * Get the current rx dboard clock rate. + * \return the clock rate in Hz + */ + virtual double get_rx_clock_rate(void) = 0; + + /*! + * Get the current tx dboard clock rate. + * \return the clock rate in Hz + */ + virtual double get_tx_clock_rate(void) = 0; + + /*! + * Enable/disable the FPGA clock. + * \param enb true to enable + */ + + virtual void enable_fpga_clock(bool enb) = 0; + + /*! + * Enable/disable the rx dboard clock. + * \param enb true to enable + */ + virtual void enable_rx_dboard_clock(bool enb) = 0; + + /*! + * Enable/disable the tx dboard clock. + * \param enb true to enable + */ + virtual void enable_tx_dboard_clock(bool enb) = 0; + + /*! + * Use the internal TCXO reference + */ + virtual void use_internal_ref(void) = 0; + + /*! + * Use the external SMA reference + */ + virtual void use_external_ref(void) = 0; + + /*! + * Use external if available, internal otherwise + */ + virtual void use_auto_ref(void) = 0; + + //! Is the reference locked? + virtual bool get_locked(void) = 0; + +}; + +#endif /* INCLUDED_B100_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/b100/codec_ctrl.cpp b/host/lib/usrp/b100/codec_ctrl.cpp new file mode 100644 index 000000000..4f9036039 --- /dev/null +++ b/host/lib/usrp/b100/codec_ctrl.cpp @@ -0,0 +1,283 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include "b100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> + +using namespace uhd; + +const gain_range_t b100_codec_ctrl::tx_pga_gain_range(-20, 0, double(0.1)); +const gain_range_t b100_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class b100_codec_ctrl_impl : public b100_codec_ctrl{ +public: + //structors + b100_codec_ctrl_impl(spi_iface::sptr iface); + ~b100_codec_ctrl_impl(void); + + //aux adc and dac control + double read_aux_adc(aux_adc_t which); + void write_aux_dac(aux_dac_t which, double volts); + + //pga gain control + void set_tx_pga_gain(double); + double get_tx_pga_gain(void); + void set_rx_pga_gain(double, char); + double get_rx_pga_gain(char); + +private: + spi_iface::sptr _iface; + ad9862_regs_t _ad9862_regs; + void send_reg(boost::uint8_t addr); + void recv_reg(boost::uint8_t addr); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +b100_codec_ctrl_impl::b100_codec_ctrl_impl(spi_iface::sptr iface){ + _iface = iface; + + //soft reset + _ad9862_regs.soft_reset = 1; + this->send_reg(0); + + //initialize the codec register settings + _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; + _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; + _ad9862_regs.soft_reset = 0; + + //setup rx side of codec + _ad9862_regs.byp_buffer_a = 1; + _ad9862_regs.byp_buffer_b = 1; + _ad9862_regs.buffer_a_pd = 1; + _ad9862_regs.buffer_b_pd = 1; + _ad9862_regs.mux_out = ad9862_regs_t::MUX_OUT_RX_MUX_MODE; //B100 uses interleaved RX->FPGA + _ad9862_regs.rx_pga_a = 0;//0x1f; //TODO bring under api control + _ad9862_regs.rx_pga_b = 0;//0x1f; //TODO bring under api control + _ad9862_regs.rx_twos_comp = 1; + _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; + + //setup tx side of codec + _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; + _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; + _ad9862_regs.tx_retime = ad9862_regs_t::TX_RETIME_CLKOUT2; + _ad9862_regs.tx_pga_gain = 199; //TODO bring under api control + _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; + _ad9862_regs.interp = ad9862_regs_t::INTERP_2; + _ad9862_regs.tx_twos_comp = 1; + _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_BYPASS; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; + _ad9862_regs.dac_a_coarse_gain = 0x3; + _ad9862_regs.dac_b_coarse_gain = 0x3; + _ad9862_regs.edges = ad9862_regs_t::EDGES_NORMAL; + + //setup the dll + _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; + _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; + _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; + + //write the register settings to the codec + for (uint8_t addr = 0; addr <= 25; addr++){ + this->send_reg(addr); + } + + //always start conversions for aux ADC + _ad9862_regs.start_a = 1; + _ad9862_regs.start_b = 1; + + //aux adc clock + _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; + this->send_reg(34); +} + +b100_codec_ctrl_impl::~b100_codec_ctrl_impl(void){ + UHD_SAFE_CALL( + //set aux dacs to zero + this->write_aux_dac(AUX_DAC_A, 0); + this->write_aux_dac(AUX_DAC_B, 0); + this->write_aux_dac(AUX_DAC_C, 0); + this->write_aux_dac(AUX_DAC_D, 0); + + //power down + _ad9862_regs.all_rx_pd = 1; + this->send_reg(1); + _ad9862_regs.tx_digital_pd = 1; + _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; + this->send_reg(8); + ) +} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void b100_codec_ctrl_impl::set_tx_pga_gain(double gain){ + int gain_word = int(mtpgw*(gain - tx_pga_gain_range.start())/(tx_pga_gain_range.stop() - tx_pga_gain_range.start())); + _ad9862_regs.tx_pga_gain = uhd::clip(gain_word, 0, mtpgw); + this->send_reg(16); +} + +double b100_codec_ctrl_impl::get_tx_pga_gain(void){ + return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.stop() - tx_pga_gain_range.start())/mtpgw) + tx_pga_gain_range.start(); +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void b100_codec_ctrl_impl::set_rx_pga_gain(double gain, char which){ + int gain_word = int(mrpgw*(gain - rx_pga_gain_range.start())/(rx_pga_gain_range.stop() - rx_pga_gain_range.start())); + gain_word = uhd::clip(gain_word, 0, mrpgw); + switch(which){ + case 'A': + _ad9862_regs.rx_pga_a = gain_word; + this->send_reg(2); + return; + case 'B': + _ad9862_regs.rx_pga_b = gain_word; + this->send_reg(3); + return; + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +double b100_codec_ctrl_impl::get_rx_pga_gain(char which){ + int gain_word; + switch(which){ + case 'A': gain_word = _ad9862_regs.rx_pga_a; break; + case 'B': gain_word = _ad9862_regs.rx_pga_b; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + return (gain_word*(rx_pga_gain_range.stop() - rx_pga_gain_range.start())/mrpgw) + rx_pga_gain_range.start(); +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static double aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low){ + return double((boost::uint16_t(high) << 2) | low)*3.3/0x3ff; +} + +double b100_codec_ctrl_impl::read_aux_adc(aux_adc_t which){ + switch(which){ + + case AUX_ADC_A1: + _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC1; + this->send_reg(34); //start conversion and select mux + this->recv_reg(28); //read the value (2 bytes, 2 reads) + this->recv_reg(29); + return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); + case AUX_ADC_A2: + _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC2; + this->send_reg(34); //start conversion and select mux + this->recv_reg(26); //read the value (2 bytes, 2 reads) + this->recv_reg(27); + return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); + + case AUX_ADC_B1: + _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC1; + this->send_reg(34); //start conversion and select mux + this->recv_reg(32); //read the value (2 bytes, 2 reads) + this->recv_reg(33); + return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); + case AUX_ADC_B2: + _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC2; + this->send_reg(34); //start conversion and select mux + this->recv_reg(30); //read the value (2 bytes, 2 reads) + this->recv_reg(31); + return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void b100_codec_ctrl_impl::write_aux_dac(aux_dac_t which, double volts){ + //special case for aux dac d (aka sigma delta word) + if (which == AUX_DAC_D){ + boost::uint16_t dac_word = uhd::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); + _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); + _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); + this->send_reg(42); + this->send_reg(43); + return; + } + + //calculate the dac word for aux dac a, b, c + boost::uint8_t dac_word = uhd::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + + //setup a lookup table for the aux dac params (reg ref, reg addr) + typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; + uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of + (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) + (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) + (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) + ; + + //set the aux dac register + UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); + boost::uint8_t *reg_ref, reg_addr; + boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; + *reg_ref = dac_word; + this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void b100_codec_ctrl_impl::send_reg(boost::uint8_t addr){ + boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); + UHD_LOGV(rarely) << "codec control write reg: " << std::hex << reg << std::endl; + _iface->transact_spi( + B100_SPI_SS_AD9862, + spi_config_t::EDGE_RISE, + reg, 16, false /*no rb*/ + ); +} + +void b100_codec_ctrl_impl::recv_reg(boost::uint8_t addr){ + boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); + UHD_LOGV(rarely) << "codec control read reg: " << std::hex << reg << std::endl; + boost::uint32_t ret = _iface->transact_spi( + B100_SPI_SS_AD9862, + spi_config_t::EDGE_RISE, + reg, 16, true /*rb*/ + ); + UHD_LOGV(rarely) << "codec control read ret: " << std::hex << boost::uint16_t(ret & 0xFF) << std::endl; + _ad9862_regs.set_reg(addr, boost::uint8_t(ret&0xff)); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +b100_codec_ctrl::sptr b100_codec_ctrl::make(spi_iface::sptr iface){ + return sptr(new b100_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/b100/codec_ctrl.hpp b/host/lib/usrp/b100/codec_ctrl.hpp new file mode 100644 index 000000000..1f7bdef09 --- /dev/null +++ b/host/lib/usrp/b100/codec_ctrl.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CODEC_CTRL_HPP +#define INCLUDED_B100_CODEC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp-e codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class b100_codec_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<b100_codec_ctrl> sptr; + + static const uhd::gain_range_t tx_pga_gain_range; + static const uhd::gain_range_t rx_pga_gain_range; + + /*! + * Make a new codec control object. + * \param iface the usrp_e iface object + * \return the codec control object + */ + static sptr make(uhd::spi_iface::sptr iface); + + //! aux adc identifier constants + enum aux_adc_t{ + AUX_ADC_A2 = 0xA2, + AUX_ADC_A1 = 0xA1, + AUX_ADC_B2 = 0xB2, + AUX_ADC_B1 = 0xB1 + }; + + /*! + * Read an auxiliary adc: + * The internals remember which aux adc was read last. + * Therefore, the aux adc switch is only changed as needed. + * \param which which of the 4 adcs + * \return a value in volts + */ + virtual double read_aux_adc(aux_adc_t which) = 0; + + //! aux dac identifier constants + enum aux_dac_t{ + AUX_DAC_A = 0xA, + AUX_DAC_B = 0xB, + AUX_DAC_C = 0xC, + AUX_DAC_D = 0xD //really the sigma delta output + }; + + /*! + * Write an auxiliary dac. + * \param which which of the 4 dacs + * \param volts the level in in volts + */ + virtual void write_aux_dac(aux_dac_t which, double volts) = 0; + + //! Set the TX PGA gain + virtual void set_tx_pga_gain(double gain) = 0; + + //! Get the TX PGA gain + virtual double get_tx_pga_gain(void) = 0; + + //! Set the RX PGA gain ('A' or 'B') + virtual void set_rx_pga_gain(double gain, char which) = 0; + + //! Get the RX PGA gain ('A' or 'B') + virtual double get_rx_pga_gain(char which) = 0; +}; + +#endif /* INCLUDED_B100_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/b100/ctrl_packet.hpp b/host/lib/usrp/b100/ctrl_packet.hpp new file mode 100644 index 000000000..bab1f0de1 --- /dev/null +++ b/host/lib/usrp/b100/ctrl_packet.hpp @@ -0,0 +1,75 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_CTRL_PACKET_HPP +#define INCLUDED_CTRL_PACKET_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <uhd/types/serial.hpp> + +typedef std::vector<boost::uint16_t> ctrl_data_t; + +/*! + * Control packet operation type + */ +enum ctrl_pkt_op_t { + CTRL_PKT_OP_WRITE = 1, + CTRL_PKT_OP_READ = 2, + CTRL_PKT_OP_READBACK = 3 +}; + +/*! + * Control packet transaction length + */ +const size_t CTRL_PACKET_LENGTH = 32; +const size_t CTRL_PACKET_HEADER_LENGTH = 8; +const size_t CTRL_PACKET_DATA_LENGTH = 24; //=length-header + +/*! + * Control packet header magic value + */ +const boost::uint8_t CTRL_PACKET_HEADER_MAGIC = 0xAA; + +/*! + * Callback triggers for readback operation + */ +//FIXME: these are not real numbers, callbacks aren't implemented yet +const boost::uint16_t CTRL_PACKET_CALLBACK_SPI = 0x0001; +const boost::uint16_t CTRL_PACKET_CALLBACK_I2C = 0x0002; +//and so on + +/*! + * Metadata structure to describe a control packet + */ +struct UHD_API ctrl_pkt_meta_t { + ctrl_pkt_op_t op; + boost::uint8_t callbacks; + boost::uint8_t seq; + boost::uint16_t len; + boost::uint32_t addr; +}; + +/*! + * Full control packet structure + */ +struct UHD_API ctrl_pkt_t { + ctrl_pkt_meta_t pkt_meta; + ctrl_data_t data; +}; + +#endif /* INCLUDED_CTRL_PACKET_HPP */ diff --git a/host/lib/usrp/b100/dboard_iface.cpp b/host/lib/usrp/b100/dboard_iface.cpp new file mode 100644 index 000000000..39ad5c5ac --- /dev/null +++ b/host/lib/usrp/b100/dboard_iface.cpp @@ -0,0 +1,258 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "gpio_core_200.hpp" +#include <uhd/types/serial.hpp> +#include "b100_regs.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> + + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class b100_dboard_iface : public dboard_iface{ +public: + + b100_dboard_iface( + wb_iface::sptr wb_iface, + i2c_iface::sptr i2c_iface, + spi_iface::sptr spi_iface, + b100_clock_ctrl::sptr clock, + b100_codec_ctrl::sptr codec + ){ + _wb_iface = wb_iface; + _i2c_iface = i2c_iface; + _spi_iface = spi_iface; + _clock = clock; + _codec = codec; + _gpio = gpio_core_200::make(_wb_iface, B100_REG_SR_ADDR(B100_SR_GPIO), B100_REG_RB_GPIO); + + //init the clock rate shadows + this->set_clock_rate(UNIT_RX, _clock->get_fpga_clock_rate()); + this->set_clock_rate(UNIT_TX, _clock->get_fpga_clock_rate()); + } + + ~b100_dboard_iface(void){ + /* NOP */ + } + + special_props_t get_special_props(void){ + special_props_t props; + props.soft_clock_divider = false; + props.mangle_i2c_addrs = false; + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, double); + double read_aux_adc(unit_t, aux_adc_t); + + void _set_pin_ctrl(unit_t, boost::uint16_t); + void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); + void _set_gpio_ddr(unit_t, boost::uint16_t); + void _set_gpio_out(unit_t, boost::uint16_t); + void set_gpio_debug(unit_t, int); + boost::uint16_t read_gpio(unit_t); + + void write_i2c(boost::uint8_t, const byte_vector_t &); + byte_vector_t read_i2c(boost::uint8_t, size_t); + + void write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + boost::uint32_t read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + void set_clock_rate(unit_t, double); + std::vector<double> get_clock_rates(unit_t); + double get_clock_rate(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + +private: + wb_iface::sptr _wb_iface; + i2c_iface::sptr _i2c_iface; + spi_iface::sptr _spi_iface; + b100_clock_ctrl::sptr _clock; + b100_codec_ctrl::sptr _codec; + gpio_core_200::sptr _gpio; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_b100_dboard_iface( + wb_iface::sptr wb_iface, + i2c_iface::sptr i2c_iface, + spi_iface::sptr spi_iface, + b100_clock_ctrl::sptr clock, + b100_codec_ctrl::sptr codec +){ + return dboard_iface::sptr(new b100_dboard_iface(wb_iface, i2c_iface, spi_iface, clock, codec)); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +void b100_dboard_iface::set_clock_rate(unit_t unit, double rate){ + switch(unit){ + case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); + case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + } +} + +std::vector<double> b100_dboard_iface::get_clock_rates(unit_t unit){ + switch(unit){ + case UNIT_RX: return _clock->get_rx_dboard_clock_rates(); + case UNIT_TX: return _clock->get_tx_dboard_clock_rates(); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +double b100_dboard_iface::get_clock_rate(unit_t unit){ + switch(unit){ + case UNIT_RX: return _clock->get_rx_clock_rate(); + case UNIT_TX: return _clock->get_tx_clock_rate(); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +void b100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ + switch(unit){ + case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); + case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + } +} + +double b100_dboard_iface::get_codec_rate(unit_t){ + return _clock->get_fpga_clock_rate(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void b100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ + return _gpio->set_pin_ctrl(unit, value); +} + +void b100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ + return _gpio->set_gpio_ddr(unit, value); +} + +void b100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ + return _gpio->set_gpio_out(unit, value); +} + +boost::uint16_t b100_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); +} + +void b100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ + return _gpio->set_atr_reg(unit, atr, value); +} + +void b100_dboard_iface::set_gpio_debug(unit_t, int){ + throw uhd::not_implemented_error("no set_gpio_debug implemented"); +} + +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ + switch(unit){ + case dboard_iface::UNIT_TX: return B100_SPI_SS_TX_DB; + case dboard_iface::UNIT_RX: return B100_SPI_SS_RX_DB; + } + UHD_THROW_INVALID_CODE_PATH(); +} + +void b100_dboard_iface::write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + _spi_iface->write_spi(unit_to_otw_spi_dev(unit), config, data, num_bits); +} + +boost::uint32_t b100_dboard_iface::read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + return _spi_iface->read_spi(unit_to_otw_spi_dev(unit), config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void b100_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ + return _i2c_iface->write_i2c(addr, bytes); +} + +byte_vector_t b100_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ + return _i2c_iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void b100_dboard_iface::write_aux_dac(dboard_iface::unit_t, aux_dac_t which, double value){ + //same aux dacs for each unit + static const uhd::dict<aux_dac_t, b100_codec_ctrl::aux_dac_t> which_to_aux_dac = map_list_of + (AUX_DAC_A, b100_codec_ctrl::AUX_DAC_A) + (AUX_DAC_B, b100_codec_ctrl::AUX_DAC_B) + (AUX_DAC_C, b100_codec_ctrl::AUX_DAC_C) + (AUX_DAC_D, b100_codec_ctrl::AUX_DAC_D) + ; + _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +double b100_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, aux_adc_t which){ + static const uhd::dict< + unit_t, uhd::dict<aux_adc_t, b100_codec_ctrl::aux_adc_t> + > unit_to_which_to_aux_adc = map_list_of + (UNIT_RX, map_list_of + (AUX_ADC_A, b100_codec_ctrl::AUX_ADC_A1) + (AUX_ADC_B, b100_codec_ctrl::AUX_ADC_B1) + ) + (UNIT_TX, map_list_of + (AUX_ADC_A, b100_codec_ctrl::AUX_ADC_A2) + (AUX_ADC_B, b100_codec_ctrl::AUX_ADC_B2) + ) + ; + return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/b100/io_impl.cpp b/host/lib/usrp/b100/io_impl.cpp new file mode 100644 index 000000000..674380cca --- /dev/null +++ b/host/lib/usrp/b100/io_impl.cpp @@ -0,0 +1,295 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "recv_packet_demuxer.hpp" +#include "validate_subdev_spec.hpp" +#include "async_packet_handler.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "usrp_commands.h" +#include "b100_impl.hpp" +#include "b100_regs.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/*********************************************************************** + * IO Implementation Details + **********************************************************************/ +struct b100_impl::io_impl{ + io_impl(void): + async_msg_fifo(1000/*messages deep*/) + { /* NOP */ } + + zero_copy_if::sptr data_transport; + bounded_buffer<async_metadata_t> async_msg_fifo; + recv_packet_demuxer::sptr demuxer; + double tick_rate; +}; + +/*********************************************************************** + * Initialize internals within this file + **********************************************************************/ +void b100_impl::io_init(void){ + + //clear fifo state machines + _fpga_ctrl->poke32(B100_REG_CLEAR_FIFO, 0); + + //allocate streamer weak ptrs containers + _rx_streamers.resize(_rx_dsps.size()); + _tx_streamers.resize(1/*known to be 1 dsp*/); + + //create new io impl + _io_impl = UHD_PIMPL_MAKE(io_impl, ()); + _io_impl->demuxer = recv_packet_demuxer::make(_data_transport, _rx_dsps.size(), B100_RX_SID_BASE); + + //now its safe to register the async callback + _fpga_ctrl->set_async_cb(boost::bind(&b100_impl::handle_async_message, this, _1)); +} + +void b100_impl::handle_async_message(managed_recv_buffer::sptr rbuf){ + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = rbuf->size()/sizeof(boost::uint32_t); + const boost::uint32_t *vrt_hdr = rbuf->cast<const boost::uint32_t *>(); + try{ + vrt::if_hdr_unpack_le(vrt_hdr, if_packet_info); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Error (handle_async_message): " << e.what() << std::endl; + } + + if (if_packet_info.sid == B100_TX_ASYNC_SID and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff(uhd::wtohx<boost::uint32_t>, metadata, if_packet_info, vrt_hdr, _io_impl->tick_rate); + + //push the message onto the queue + _io_impl->async_msg_fifo.push_with_pop_on_full(metadata); + + //print some fastpath messages + standard_async_msg_prints(metadata); + } + else UHD_MSG(error) << "Unknown async packet" << std::endl; +} + +void b100_impl::update_rates(void){ + const fs_path mb_path = "/mboards/0"; + _tree->access<double>(mb_path / "tick_rate").update(); + + //and now that the tick rate is set, init the host rates to something + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ + _tree->access<double>(mb_path / "rx_dsps" / name / "rate" / "value").update(); + } + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ + _tree->access<double>(mb_path / "tx_dsps" / name / "rate" / "value").update(); + } +} + +void b100_impl::update_tick_rate(const double rate){ + _io_impl->tick_rate = rate; + + //update the tick rate on all existing streamers -> thread safe + for (size_t i = 0; i < _rx_streamers.size(); i++){ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); + if (my_streamer.get() == NULL) continue; + my_streamer->set_tick_rate(rate); + } + for (size_t i = 0; i < _tx_streamers.size(); i++){ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[i].lock()); + if (my_streamer.get() == NULL) continue; + my_streamer->set_tick_rate(rate); + } +} + +void b100_impl::update_rx_samp_rate(const size_t dspno, const double rate){ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[dspno].lock()); + if (my_streamer.get() == NULL) return; + + my_streamer->set_samp_rate(rate); + const double adj = _rx_dsps[dspno]->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void b100_impl::update_tx_samp_rate(const size_t dspno, const double rate){ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[dspno].lock()); + if (my_streamer.get() == NULL) return; + + my_streamer->set_samp_rate(rate); + const double adj = _tx_dsp->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void b100_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ + fs_path root = "/mboards/0/dboards"; + + //sanity checking + validate_subdev_spec(_tree, spec, "rx"); + + //setup mux for this spec + bool fe_swapped = false; + for (size_t i = 0; i < spec.size(); i++){ + const std::string conn = _tree->access<std::string>(root / spec[i].db_name / "rx_frontends" / spec[i].sd_name / "connection").get(); + if (i == 0 and (conn == "QI" or conn == "Q")) fe_swapped = true; + _rx_dsps[i]->set_mux(conn, fe_swapped); + } + _rx_fe->set_mux(fe_swapped); +} + +void b100_impl::update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ + fs_path root = "/mboards/0/dboards"; + + //sanity checking + validate_subdev_spec(_tree, spec, "tx"); + + //set the mux for this spec + const std::string conn = _tree->access<std::string>(root / spec[0].db_name / "tx_frontends" / spec[0].sd_name / "connection").get(); + _tx_fe->set_mux(conn); +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool b100_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +){ + boost::this_thread::disable_interruption di; //disable because the wait can throw + return _io_impl->async_msg_fifo.pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr b100_impl::get_rx_stream(const uhd::stream_args_t &args_){ + stream_args_t args = args_; + + //setup defaults for unspecified values + args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = _data_transport->get_recv_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + + //init some streamer stuff + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_unpacker(&vrt::if_hdr_unpack_le); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_le"; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //bind callbacks for the handler + for (size_t chan_i = 0; chan_i < args.channels.size(); chan_i++){ + const size_t dsp = args.channels[chan_i]; + _rx_dsps[dsp]->set_nsamps_per_packet(spp); //seems to be a good place to set this + _rx_dsps[dsp]->setup(args); + my_streamer->set_xport_chan_get_buff(chan_i, boost::bind( + &recv_packet_demuxer::get_recv_buff, _io_impl->demuxer, dsp, _1 + ), true /*flush*/); + my_streamer->set_overflow_handler(chan_i, boost::bind( + &rx_dsp_core_200::handle_overflow, _rx_dsps[dsp] + )); + _rx_streamers[dsp] = my_streamer; //store weak pointer + } + + //sets all tick and samp rates on this streamer + this->update_rates(); + + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr b100_impl::get_tx_stream(const uhd::stream_args_t &args_){ + stream_args_t args = args_; + + //setup defaults for unspecified values + args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().sid) //no stream id ever used + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + static const size_t bpp = _data_transport->get_send_frame_size() - hdr_size; + const size_t spp = bpp/convert::get_bytes_per_item(args.otw_format); + + //make the new streamer given the samples per packet + boost::shared_ptr<sph::send_packet_streamer> my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + + //init some streamer stuff + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_packer(&vrt::if_hdr_pack_le); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_le"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //bind callbacks for the handler + for (size_t chan_i = 0; chan_i < args.channels.size(); chan_i++){ + const size_t dsp = args.channels[chan_i]; + UHD_ASSERT_THROW(dsp == 0); //always 0 + _tx_dsp->setup(args); + my_streamer->set_xport_chan_get_buff(chan_i, boost::bind( + &zero_copy_if::get_send_buff, _data_transport, _1 + )); + _tx_streamers[dsp] = my_streamer; //store weak pointer + } + + //sets all tick and samp rates on this streamer + this->update_rates(); + + return my_streamer; +} diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt new file mode 100644 index 000000000..9abd34afa --- /dev/null +++ b/host/lib/usrp/common/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +IF(ENABLE_USB) + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/../firmware/fx2/common) + + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/fx2_ctrl.cpp + ) +ENDIF(ENABLE_USB) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/apply_corrections.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp +) diff --git a/host/lib/usrp/common/apply_corrections.cpp b/host/lib/usrp/common/apply_corrections.cpp new file mode 100644 index 000000000..6aee00753 --- /dev/null +++ b/host/lib/usrp/common/apply_corrections.cpp @@ -0,0 +1,217 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "apply_corrections.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/csv.hpp> +#include <uhd/types/dict.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/mutex.hpp> +#include <cstdio> +#include <complex> +#include <fstream> + +namespace fs = boost::filesystem; + +boost::mutex corrections_mutex; + +/*********************************************************************** + * Helper routines + **********************************************************************/ +static double linear_interp(double x, double x0, double y0, double x1, double y1){ + return y0 + (x - x0)*(y1 - y0)/(x1 - x0); +} + +/*********************************************************************** + * FE apply corrections implementation + **********************************************************************/ +struct fe_cal_t{ + double lo_freq; + double iq_corr_real; + double iq_corr_imag; +}; + +static bool fe_cal_comp(fe_cal_t a, fe_cal_t b){ + return (a.lo_freq < b.lo_freq); +} + +static uhd::dict<std::string, std::vector<fe_cal_t> > fe_cal_cache; + +static std::complex<double> get_fe_dc_correction( + const std::string &key, const double lo_freq +){ + const std::vector<fe_cal_t> &datas = fe_cal_cache[key]; + if (datas.empty()) throw uhd::runtime_error("empty calibration table " + key); + + //search for lo freq + size_t lo_index = 0; + size_t hi_index = datas.size()-1; + for (size_t i = 0; i < datas.size(); i++){ + if (datas[i].lo_freq > lo_freq){ + hi_index = i; + break; + } + lo_index = i; + } + + if (lo_index == 0) return std::complex<double>(datas[lo_index].iq_corr_real, datas[lo_index].iq_corr_imag); + if (hi_index == lo_index) return std::complex<double>(datas[hi_index].iq_corr_real, datas[hi_index].iq_corr_imag); + + //interpolation time + return std::complex<double>( + linear_interp(lo_freq, datas[lo_index].lo_freq, datas[lo_index].iq_corr_real, datas[hi_index].lo_freq, datas[hi_index].iq_corr_real), + linear_interp(lo_freq, datas[lo_index].lo_freq, datas[lo_index].iq_corr_imag, datas[hi_index].lo_freq, datas[hi_index].iq_corr_imag) + ); +} + +static std::complex<double> get_fe_iq_correction( + const std::string &key, const double lo_freq +){ + const std::vector<fe_cal_t> &datas = fe_cal_cache[key]; + if (datas.empty()) throw uhd::runtime_error("empty calibration table " + key); + + //search for lo freq + size_t lo_index = 0; + size_t hi_index = datas.size()-1; + for (size_t i = 0; i < datas.size(); i++){ + if (datas[i].lo_freq > lo_freq){ + hi_index = i; + break; + } + lo_index = i; + } + + if (lo_index == 0) return std::complex<double>(datas[lo_index].iq_corr_real, datas[lo_index].iq_corr_imag); + if (hi_index == lo_index) return std::complex<double>(datas[hi_index].iq_corr_real, datas[hi_index].iq_corr_imag); + + const std::complex<double> lo_val(datas[lo_index].iq_corr_real, datas[lo_index].iq_corr_imag); + const std::complex<double> hi_val(datas[hi_index].iq_corr_real, datas[hi_index].iq_corr_imag); + + //interpolation time + return std::polar<double>( + linear_interp(lo_freq, datas[lo_index].lo_freq, std::abs(lo_val), datas[hi_index].lo_freq, std::abs(hi_val)), + linear_interp(lo_freq, datas[lo_index].lo_freq, std::arg(lo_val), datas[hi_index].lo_freq, std::arg(hi_val)) + ); +} + +static void apply_fe_corrections( + uhd::property_tree::sptr sub_tree, + const uhd::fs_path &db_path, + const uhd::fs_path &fe_path, + const std::string &file_prefix, + const double lo_freq +){ + //extract eeprom serial + const uhd::usrp::dboard_eeprom_t db_eeprom = sub_tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get(); + + //make the calibration file path + const fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd" / "cal" / (file_prefix + db_eeprom.serial + ".csv"); + if (not fs::exists(cal_data_path)) return; + + //parse csv file or get from cache + if (not fe_cal_cache.has_key(cal_data_path.string())){ + std::ifstream cal_data(cal_data_path.string().c_str()); + const uhd::csv::rows_type rows = uhd::csv::to_rows(cal_data); + + bool read_data = false, skip_next = false;; + std::vector<fe_cal_t> datas; + BOOST_FOREACH(const uhd::csv::row_type &row, rows){ + if (not read_data and not row.empty() and row[0] == "DATA STARTS HERE"){ + read_data = true; + skip_next = true; + continue; + } + if (not read_data) continue; + if (skip_next){ + skip_next = false; + continue; + } + fe_cal_t data; + std::sscanf(row[0].c_str(), "%lf" , &data.lo_freq); + std::sscanf(row[1].c_str(), "%lf" , &data.iq_corr_real); + std::sscanf(row[2].c_str(), "%lf" , &data.iq_corr_imag); + datas.push_back(data); + } + std::sort(datas.begin(), datas.end(), fe_cal_comp); + fe_cal_cache[cal_data_path.string()] = datas; + UHD_MSG(status) << "Loaded " << cal_data_path.string() << std::endl; + + } + + if (file_prefix.find("dc_cal") != std::string::npos){ + sub_tree->access<std::complex<double> >(fe_path) + .set(get_fe_dc_correction(cal_data_path.string(), lo_freq)); + } + else if (file_prefix.find("iq_cal") != std::string::npos){ + sub_tree->access<std::complex<double> >(fe_path) + .set(get_fe_iq_correction(cal_data_path.string(), lo_freq)); + } + else throw uhd::runtime_error("could not determine interpolation function"); +} + +/*********************************************************************** + * Wrapper routines with nice try/catch + print + **********************************************************************/ +void uhd::usrp::apply_tx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const std::string &slot, //name of dboard slot + const double lo_freq //actual lo freq +){ + boost::mutex::scoped_lock l(corrections_mutex); + try{ + apply_fe_corrections( + sub_tree, + "dboards/" + slot + "/tx_eeprom", + "tx_frontends/" + slot + "/iq_balance/value", + "tx_iq_cal_v0.1_", + lo_freq + ); + apply_fe_corrections( + sub_tree, + "dboards/" + slot + "/tx_eeprom", + "tx_frontends/" + slot + "/dc_offset/value", + "tx_dc_cal_v0.1_", + lo_freq + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl; + } +} + +void uhd::usrp::apply_rx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const std::string &slot, //name of dboard slot + const double lo_freq //actual lo freq +){ + boost::mutex::scoped_lock l(corrections_mutex); + try{ + apply_fe_corrections( + sub_tree, + "dboards/" + slot + "/rx_eeprom", + "rx_frontends/" + slot + "/iq_balance/value", + "rx_iq_cal_v0.1_", + lo_freq + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Failure in apply_rx_fe_corrections: " << e.what() << std::endl; + } +} diff --git a/host/lib/usrp/common/apply_corrections.hpp b/host/lib/usrp/common/apply_corrections.hpp new file mode 100644 index 000000000..c516862d1 --- /dev/null +++ b/host/lib/usrp/common/apply_corrections.hpp @@ -0,0 +1,41 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP + +#include <uhd/config.hpp> +#include <uhd/property_tree.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + + void apply_tx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const std::string &slot, //name of dboard slot + const double tx_lo_freq //actual lo freq + ); + + void apply_rx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const std::string &slot, //name of dboard slot + const double rx_lo_freq //actual lo freq + ); + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP */ diff --git a/host/lib/usrp/common/async_packet_handler.hpp b/host/lib/usrp/common/async_packet_handler.hpp new file mode 100644 index 000000000..fef03483f --- /dev/null +++ b/host/lib/usrp/common/async_packet_handler.hpp @@ -0,0 +1,71 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_ASYNC_PACKET_HANDLER_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_ASYNC_PACKET_HANDLER_HPP + +#include <uhd/config.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> + +namespace uhd{ namespace usrp{ + + template <typename to_host_type> + void load_metadata_from_buff( + const to_host_type &to_host, + async_metadata_t &metadata, + const transport::vrt::if_packet_info_t &if_packet_info, + const boost::uint32_t *vrt_hdr, + const double tick_rate, + const size_t channel = 0 + ){ + const boost::uint32_t *payload = vrt_hdr + if_packet_info.num_header_words32; + + //load into metadata + metadata.channel = channel; + metadata.has_time_spec = if_packet_info.has_tsf; + metadata.time_spec = time_spec_t::from_ticks(if_packet_info.tsf, tick_rate); + metadata.event_code = async_metadata_t::event_code_t(to_host(payload[0]) & 0xff); + + //load user payload + for (size_t i = 1; i < if_packet_info.num_payload_words32; i++){ + if (i-1 == 4) break; //limit of 4 words32 + metadata.user_payload[i-1] = to_host(payload[i]); + } + } + + UHD_INLINE void standard_async_msg_prints(const async_metadata_t &metadata) + { + if (metadata.event_code & + ( async_metadata_t::EVENT_CODE_UNDERFLOW + | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET) + ) UHD_MSG(fastpath) << "U"; + else if (metadata.event_code & + ( async_metadata_t::EVENT_CODE_SEQ_ERROR + | async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST) + ) UHD_MSG(fastpath) << "S"; + else if (metadata.event_code & + async_metadata_t::EVENT_CODE_TIME_ERROR + ) UHD_MSG(fastpath) << "L"; + } + + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_ASYNC_PACKET_HANDLER_HPP */ diff --git a/host/lib/usrp/common/fx2_ctrl.cpp b/host/lib/usrp/common/fx2_ctrl.cpp new file mode 100644 index 000000000..7b8920eb1 --- /dev/null +++ b/host/lib/usrp/common/fx2_ctrl.cpp @@ -0,0 +1,472 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "fx2_ctrl.hpp" +#include "usrp_commands.h" +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/usb_control.hpp> +#include <boost/functional/hash.hpp> +#include <boost/thread/thread.hpp> +#include <boost/cstdint.hpp> +#include <fstream> +#include <sstream> +#include <string> +#include <vector> +#include <cstring> + +using namespace uhd; +using namespace uhd::usrp; + +#define FX2_FIRMWARE_LOAD 0xa0 + +static const bool load_img_msg = true; + +typedef boost::uint32_t hash_type; + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +/*! + * Create a file hash + * The hash will be used to identify the loaded firmware and fpga image + * \param filename file used to generate hash value + * \return hash value in a size_t type + */ +static hash_type generate_hash(const char *filename) +{ + std::ifstream file(filename); + if (not file){ + throw uhd::io_error(std::string("cannot open input file ") + filename); + } + + size_t hash = 0; + + char ch; + while (file.get(ch)) { + boost::hash_combine(hash, ch); + } + + if (not file.eof()){ + throw uhd::io_error(std::string("file error ") + filename); + } + + file.close(); + return hash_type(hash); +} + + +/*! + * Verify checksum of a Intel HEX record + * \param record a line from an Intel HEX file + * \return true if record is valid, false otherwise + */ +static bool checksum(std::string *record) +{ + + size_t len = record->length(); + unsigned int i; + unsigned char sum = 0; + unsigned int val; + + for (i = 1; i < len; i += 2) { + std::istringstream(record->substr(i, 2)) >> std::hex >> val; + sum += val; + } + + if (sum == 0) + return true; + else + return false; +} + + +/*! + * Parse Intel HEX record + * + * \param record a line from an Intel HEX file + * \param len output length of record + * \param addr output address + * \param type output type + * \param data output data + * \return true if record is sucessfully read, false on error + */ +bool parse_record(std::string *record, unsigned int &len, + unsigned int &addr, unsigned int &type, + unsigned char* data) +{ + unsigned int i; + std::string _data; + unsigned int val; + + if (record->substr(0, 1) != ":") + return false; + + std::istringstream(record->substr(1, 2)) >> std::hex >> len; + std::istringstream(record->substr(3, 4)) >> std::hex >> addr; + std::istringstream(record->substr(7, 2)) >> std::hex >> type; + + for (i = 0; i < len; i++) { + std::istringstream(record->substr(9 + 2 * i, 2)) >> std::hex >> val; + data[i] = (unsigned char) val; + } + + return true; +} + + +/*! + * USRP control implementation for device discovery and configuration + */ +class fx2_ctrl_impl : public fx2_ctrl { +public: + fx2_ctrl_impl(uhd::transport::usb_control::sptr ctrl_transport) + { + _ctrl_transport = ctrl_transport; + } + + void usrp_fx2_reset(void){ + unsigned char reset_y = 1; + unsigned char reset_n = 0; + usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, &reset_y, 1); + usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, &reset_n, 1); + //wait for things to settle + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + } + + void usrp_load_firmware(std::string filestring, bool force) + { + const char *filename = filestring.c_str(); + + hash_type hash = generate_hash(filename); + + hash_type loaded_hash; usrp_get_firmware_hash(loaded_hash); + + if (not force and (hash == loaded_hash)) return; + + //FIXME: verify types + unsigned int len; + unsigned int addr; + unsigned int type; + unsigned char data[512]; + + std::ifstream file; + file.open(filename, std::ifstream::in); + + if (!file.good()) { + throw uhd::io_error("usrp_load_firmware: cannot open firmware input file"); + } + + unsigned char reset_y = 1; + unsigned char reset_n = 0; + + //hit the reset line + if (load_img_msg) UHD_MSG(status) << "Loading firmware image: " << filestring << "..." << std::flush; + usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, &reset_y, 1); + + while (!file.eof()) { + std::string record; + file >> record; + + //check for valid record + if (not checksum(&record) or not parse_record(&record, len, addr, type, data)) { + throw uhd::io_error("usrp_load_firmware: bad record checksum"); + } + + //type 0x00 is data + if (type == 0x00) { + int ret = usrp_control_write(FX2_FIRMWARE_LOAD, addr, 0, data, len); + if (ret < 0) throw uhd::io_error("usrp_load_firmware: usrp_control_write failed"); + } + //type 0x01 is end + else if (type == 0x01) { + usrp_set_firmware_hash(hash); //set hash before reset + usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, &reset_n, 1); + file.close(); + + //wait for things to settle + boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); + if (load_img_msg) UHD_MSG(status) << " done" << std::endl; + return; + } + //type anything else is unhandled + else { + throw uhd::io_error("usrp_load_firmware: unsupported record"); + } + } + + //file did not end + throw uhd::io_error("usrp_load_firmware: bad record"); + } + + void usrp_init(void){ + //disable + usrp_rx_enable(false); + usrp_tx_enable(false); + + //toggle resets + usrp_rx_reset(true); + usrp_tx_reset(true); + usrp_rx_reset(false); + usrp_tx_reset(false); + } + + void usrp_load_fpga(std::string filestring) + { + const char *filename = filestring.c_str(); + + hash_type hash = generate_hash(filename); + + hash_type loaded_hash; usrp_get_fpga_hash(loaded_hash); + + if (hash == loaded_hash) return; + const int ep0_size = 64; + unsigned char buf[ep0_size]; + + if (load_img_msg) UHD_MSG(status) << "Loading FPGA image: " << filestring << "..." << std::flush; + std::ifstream file; + file.open(filename, std::ios::in | std::ios::binary); + if (not file.good()) { + throw uhd::io_error("usrp_load_fpga: cannot open fpga input file"); + } + + usrp_fpga_reset(true); //holding the fpga in reset while loading + + if (usrp_control_write_cmd(VRQ_FPGA_LOAD, 0, FL_BEGIN) < 0) { + throw uhd::io_error("usrp_load_fpga: fpga load error"); + } + + while (not file.eof()) { + file.read((char *)buf, sizeof(buf)); + const std::streamsize n = file.gcount(); + if(n == 0) continue; + int ret = usrp_control_write(VRQ_FPGA_LOAD, 0, FL_XFER, buf, boost::uint16_t(n)); + if (ret < 0 or std::streamsize(ret) != n) { + throw uhd::io_error("usrp_load_fpga: fpga load error"); + } + } + + if (usrp_control_write_cmd(VRQ_FPGA_LOAD, 0, FL_END) < 0) { + throw uhd::io_error("usrp_load_fpga: fpga load error"); + } + + usrp_set_fpga_hash(hash); + + usrp_fpga_reset(false); //done loading, take fpga out of reset + + file.close(); + if (load_img_msg) UHD_MSG(status) << " done" << std::endl; + } + + void usrp_load_eeprom(std::string filestring) + { + if (load_img_msg) UHD_MSG(status) << "Loading EEPROM image: " << filestring << "..." << std::flush; + const char *filename = filestring.c_str(); + const boost::uint16_t i2c_addr = 0x50; + + unsigned int addr; + unsigned char data[256]; + unsigned char sendbuf[17]; + + std::ifstream file; + file.open(filename, std::ifstream::in); + + if (not file.good()) { + throw uhd::io_error("usrp_load_eeprom: cannot open EEPROM input file"); + } + + file.read((char *)data, 256); + std::streamsize len = file.gcount(); + + if(len == 256) { + throw uhd::io_error("usrp_load_eeprom: image size too large"); + } + + const int pagesize = 16; + addr = 0; + while(len > 0) { + sendbuf[0] = addr; + memcpy(sendbuf+1, &data[addr], len > pagesize ? pagesize : size_t(len)); + int ret = usrp_i2c_write(i2c_addr, sendbuf, (len > pagesize ? pagesize : size_t(len))+1); + if (ret < 0) { + throw uhd::io_error("usrp_load_eeprom: usrp_i2c_write failed"); + } + addr += pagesize; + len -= pagesize; + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + file.close(); + if (load_img_msg) UHD_MSG(status) << " done" << std::endl; + } + + + void usrp_set_led(int led_num, bool on) + { + UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_SET_LED, on, led_num) >= 0); + } + + + void usrp_get_firmware_hash(hash_type &hash) + { + UHD_ASSERT_THROW(usrp_control_read(0xa0, USRP_HASH_SLOT_0_ADDR, 0, + (unsigned char*) &hash, sizeof(hash)) >= 0); + } + + + void usrp_set_firmware_hash(hash_type hash) + { + UHD_ASSERT_THROW(usrp_control_write(0xa0, USRP_HASH_SLOT_0_ADDR, 0, + (unsigned char*) &hash, sizeof(hash)) >= 0); + + } + + + void usrp_get_fpga_hash(hash_type &hash) + { + UHD_ASSERT_THROW(usrp_control_read(0xa0, USRP_HASH_SLOT_1_ADDR, 0, + (unsigned char*) &hash, sizeof(hash)) >= 0); + } + + + void usrp_set_fpga_hash(hash_type hash) + { + UHD_ASSERT_THROW(usrp_control_write(0xa0, USRP_HASH_SLOT_1_ADDR, 0, + (unsigned char*) &hash, sizeof(hash)) >= 0); + } + + void usrp_tx_enable(bool on) + { + UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_TX_ENABLE, on, 0) >= 0); + } + + + void usrp_rx_enable(bool on) + { + UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_RX_ENABLE, on, 0) >= 0); + } + + + void usrp_tx_reset(bool on) + { + UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_TX_RESET, on, 0) >= 0); + } + + + void usrp_rx_reset(bool on) + { + UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_RX_RESET, on, 0) >= 0); + } + + void usrp_fpga_reset(bool on) + { + UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_RESET, on, 0) >= 0); + } + + int usrp_control_write(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) + { + return _ctrl_transport->submit(VRT_VENDOR_OUT, // bmReqeustType + request, // bRequest + value, // wValue + index, // wIndex + buff, // data + length); // wLength + } + + + int usrp_control_read(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) + { + return _ctrl_transport->submit(VRT_VENDOR_IN, // bmReqeustType + request, // bRequest + value, // wValue + index, // wIndex + buff, // data + length); // wLength + } + + + int usrp_control_write_cmd(boost::uint8_t request, boost::uint16_t value, boost::uint16_t index) + { + return usrp_control_write(request, value, index, 0, 0); + } + + int usrp_i2c_write(boost::uint16_t i2c_addr, unsigned char *buf, boost::uint16_t len) + { + return usrp_control_write(VRQ_I2C_WRITE, i2c_addr, 0, buf, len); + } + + int usrp_i2c_read(boost::uint16_t i2c_addr, unsigned char *buf, boost::uint16_t len) + { + return usrp_control_read(VRQ_I2C_READ, i2c_addr, 0, buf, len); + } + + static const bool iface_debug = false; + static const size_t max_i2c_data_bytes = 64; + + void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes) + { + UHD_ASSERT_THROW(bytes.size() < max_i2c_data_bytes); + + unsigned char buff[max_i2c_data_bytes] = {}; + std::copy(bytes.begin(), bytes.end(), buff); + + int ret = this->usrp_i2c_write(addr & 0xff, + buff, + bytes.size()); + + if (iface_debug && (ret < 0)) + uhd::runtime_error("USRP: failed i2c write"); + } + + byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes) + { + UHD_ASSERT_THROW(num_bytes < max_i2c_data_bytes); + + unsigned char buff[max_i2c_data_bytes] = {}; + int ret = this->usrp_i2c_read(addr & 0xff, + buff, + num_bytes); + + if (iface_debug && ((ret < 0) || (unsigned)ret < (num_bytes))) + uhd::runtime_error("USRP: failed i2c read"); + + byte_vector_t out_bytes; + for (size_t i = 0; i < num_bytes; i++) + out_bytes.push_back(buff[i]); + + return out_bytes; + } + + +private: + uhd::transport::usb_control::sptr _ctrl_transport; +}; + +/*********************************************************************** + * Public make function for fx2_ctrl interface + **********************************************************************/ +fx2_ctrl::sptr fx2_ctrl::make(uhd::transport::usb_control::sptr ctrl_transport){ + return sptr(new fx2_ctrl_impl(ctrl_transport)); +} + diff --git a/host/lib/usrp/common/fx2_ctrl.hpp b/host/lib/usrp/common/fx2_ctrl.hpp new file mode 100644 index 000000000..f2e060862 --- /dev/null +++ b/host/lib/usrp/common/fx2_ctrl.hpp @@ -0,0 +1,129 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_FX2_CTRL_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_FX2_CTRL_HPP + +#include <uhd/transport/usb_control.hpp> +#include <uhd/types/serial.hpp> //i2c iface +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +namespace uhd{ namespace usrp{ + +class fx2_ctrl : boost::noncopyable, public uhd::i2c_iface{ +public: + typedef boost::shared_ptr<fx2_ctrl> sptr; + + /*! + * Make a usrp control object from a control transport + * \param ctrl_transport a USB control transport + * \return a new usrp control object + */ + static sptr make(uhd::transport::usb_control::sptr ctrl_transport); + + //! Call init after the fpga is loaded + virtual void usrp_init(void) = 0; + + //! For emergency situations + virtual void usrp_fx2_reset(void) = 0; + + /*! + * Load firmware in Intel HEX Format onto device + * \param filename name of firmware file + * \param force reload firmware if already loaded + */ + virtual void usrp_load_firmware(std::string filename, + bool force = false) = 0; + + /*! + * Load fpga file onto usrp + * \param filename name of fpga image + */ + virtual void usrp_load_fpga(std::string filename) = 0; + + /*! + * Load USB descriptor file in Intel HEX format into EEPROM + * \param filename name of EEPROM image + */ + virtual void usrp_load_eeprom(std::string filestring) = 0; + + /*! + * Submit an IN transfer + * \param request device specific request + * \param value device specific field + * \param index device specific field + * \param buff buffer to place data + * \return number of bytes read or error + */ + virtual int usrp_control_read(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) = 0; + + /*! + * Submit an OUT transfer + * \param request device specific request + * \param value device specific field + * \param index device specific field + * \param buff buffer of data to be sent + * \return number of bytes written or error + */ + virtual int usrp_control_write(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) = 0; + + /*! + * Perform an I2C write + * \param i2c_addr I2C device address + * \param buf data to be written + * \param len length of data in bytes + * \return number of bytes written or error + */ + + virtual int usrp_i2c_write(boost::uint16_t i2c_addr, + unsigned char *buf, + boost::uint16_t len) = 0; + + /*! + * Perform an I2C read + * \param i2c_addr I2C device address + * \param buf data to be read + * \param len length of data in bytes + * \return number of bytes read or error + */ + + virtual int usrp_i2c_read(boost::uint16_t i2c_addr, + unsigned char *buf, + boost::uint16_t len) = 0; + + //! enable/disable the rx path + virtual void usrp_rx_enable(bool on) = 0; + + //! enable/disable the tx path + virtual void usrp_tx_enable(bool on) = 0; + + //! reset the fpga + virtual void usrp_fpga_reset(bool on) = 0; +}; + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_FX2_CTRL_HPP */ diff --git a/host/lib/usrp/common/recv_packet_demuxer.cpp b/host/lib/usrp/common/recv_packet_demuxer.cpp new file mode 100644 index 000000000..f2cfe3bb0 --- /dev/null +++ b/host/lib/usrp/common/recv_packet_demuxer.cpp @@ -0,0 +1,87 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "recv_packet_demuxer.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/thread/mutex.hpp> +#include <queue> +#include <deque> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static UHD_INLINE boost::uint32_t extract_sid(managed_recv_buffer::sptr &buff){ + //ASSUME that the data is in little endian format + return uhd::wtohx(buff->cast<const boost::uint32_t *>()[1]); +} + +class recv_packet_demuxer_impl : public uhd::usrp::recv_packet_demuxer{ +public: + recv_packet_demuxer_impl( + transport::zero_copy_if::sptr transport, + const size_t size, + const boost::uint32_t sid_base + ): + _transport(transport), _sid_base(sid_base), _queues(size) + { + /* NOP */ + } + + managed_recv_buffer::sptr get_recv_buff(const size_t index, const double timeout){ + boost::mutex::scoped_lock lock(_mutex); + managed_recv_buffer::sptr buff; + + //there is already an entry in the queue, so pop that + if (not _queues[index].wrapper.empty()){ + std::swap(buff, _queues[index].wrapper.front()); + _queues[index].wrapper.pop(); + return buff; + } + + while (true){ + //otherwise call into the transport + buff = _transport->get_recv_buff(timeout); + if (buff.get() == NULL) return buff; //timeout + + //check the stream id to know which channel + const size_t rx_index = extract_sid(buff) - _sid_base; + if (rx_index == index) return buff; //got expected message + + //otherwise queue and try again + if (rx_index < _queues.size()) _queues[rx_index].wrapper.push(buff); + else UHD_MSG(error) << "Got a data packet with unknown SID " << extract_sid(buff) << std::endl; + } + } + +private: + transport::zero_copy_if::sptr _transport; + const boost::uint32_t _sid_base; + boost::mutex _mutex; + struct channel_guts_type{ + channel_guts_type(void): wrapper(container){} + std::deque<managed_recv_buffer::sptr> container; + std::queue<managed_recv_buffer::sptr> wrapper; + }; + std::vector<channel_guts_type> _queues; +}; + +recv_packet_demuxer::sptr recv_packet_demuxer::make(transport::zero_copy_if::sptr transport, const size_t size, const boost::uint32_t sid_base){ + return sptr(new recv_packet_demuxer_impl(transport, size, sid_base)); +} diff --git a/host/lib/usrp/common/recv_packet_demuxer.hpp b/host/lib/usrp/common/recv_packet_demuxer.hpp new file mode 100644 index 000000000..fde756d27 --- /dev/null +++ b/host/lib/usrp/common/recv_packet_demuxer.hpp @@ -0,0 +1,41 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_RECV_PACKET_DEMUXER_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_RECV_PACKET_DEMUXER_HPP + +#include <uhd/config.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/cstdint.hpp> + +namespace uhd{ namespace usrp{ + + class recv_packet_demuxer{ + public: + typedef boost::shared_ptr<recv_packet_demuxer> sptr; + + //! Make a new demuxer from a transport and parameters + static sptr make(transport::zero_copy_if::sptr transport, const size_t size, const boost::uint32_t sid_base); + + //! Get a buffer at the given index from the transport + virtual transport::managed_recv_buffer::sptr get_recv_buff(const size_t index, const double timeout) = 0; + }; + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_RECV_PACKET_DEMUXER_HPP */ diff --git a/host/lib/usrp/common/validate_subdev_spec.cpp b/host/lib/usrp/common/validate_subdev_spec.cpp new file mode 100644 index 000000000..fab40b204 --- /dev/null +++ b/host/lib/usrp/common/validate_subdev_spec.cpp @@ -0,0 +1,73 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "validate_subdev_spec.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +namespace uhd{ namespace usrp{ + + static std::ostream& operator<< (std::ostream &out, const subdev_spec_pair_t &pair){ + out << pair.db_name << ":" << pair.sd_name; + return out; + } + +}} + +void uhd::usrp::validate_subdev_spec( + property_tree::sptr tree, + const subdev_spec_t &spec, + const std::string &type, + const std::string &mb +){ + const size_t num_dsps = tree->list(str(boost::format("/mboards/%s/%s_dsps") % mb % type)).size(); + + //sanity checking on the length + if (spec.size() == 0) throw uhd::value_error(str(boost::format( + "Empty %s subdevice specification is not supported.\n" + ) % type)); + if (spec.size() > num_dsps) throw uhd::value_error(str(boost::format( + "The subdevice specification \"%s\" is too long.\n" + "The user specified %u channels, but there are only %u %s dsps on mboard %s.\n" + ) % spec.to_string() % spec.size() % num_dsps % type % mb)); + + //make a list of all possible specs + subdev_spec_t all_specs; + BOOST_FOREACH(const std::string &db, tree->list(str(boost::format("/mboards/%s/dboards") % mb))){ + BOOST_FOREACH(const std::string &sd, tree->list(str(boost::format("/mboards/%s/dboards/%s/%s_frontends") % mb % db % type))){ + all_specs.push_back(subdev_spec_pair_t(db, sd)); + } + } + + //validate that the spec is possible + BOOST_FOREACH(const subdev_spec_pair_t &pair, spec){ + uhd::assert_has(all_specs, pair, str(boost::format("%s subdevice specification on mboard %s") % type % mb)); + } + + //enable selected frontends, disable others + BOOST_FOREACH(const subdev_spec_pair_t &pair, all_specs){ + const bool enb = uhd::has(spec, pair); + tree->access<bool>(str(boost::format( + "/mboards/%s/dboards/%s/%s_frontends/%s/enabled" + ) % mb % pair.db_name % type % pair.sd_name)).set(enb); + } +} diff --git a/host/lib/usrp/common/validate_subdev_spec.hpp b/host/lib/usrp/common/validate_subdev_spec.hpp new file mode 100644 index 000000000..7d9e2c309 --- /dev/null +++ b/host/lib/usrp/common/validate_subdev_spec.hpp @@ -0,0 +1,38 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_VALIDATE_SUBDEV_SPEC_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_VALIDATE_SUBDEV_SPEC_HPP + +#include <uhd/config.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/property_tree.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + + //! Validate a subdev spec against a property tree + void validate_subdev_spec( + property_tree::sptr tree, + const subdev_spec_t &spec, + const std::string &type, //rx or tx + const std::string &mb = "0" + ); + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_VALIDATE_SUBDEV_SPEC_HPP */ diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt new file mode 100644 index 000000000..aa5f0bcbb --- /dev/null +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# Copyright 2011-2012 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/gpio_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/i2c_core_100.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spi_core_100.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/time64_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tx_frontend_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_200.cpp +) diff --git a/host/lib/usrp/cores/gpio_core_200.cpp b/host/lib/usrp/cores/gpio_core_200.cpp new file mode 100644 index 000000000..d756097ff --- /dev/null +++ b/host/lib/usrp/cores/gpio_core_200.cpp @@ -0,0 +1,100 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "gpio_core_200.hpp" +#include <uhd/types/dict.hpp> + +#define REG_GPIO_IDLE _base + 0 +#define REG_GPIO_RX_ONLY _base + 4 +#define REG_GPIO_TX_ONLY _base + 8 +#define REG_GPIO_BOTH _base + 12 +#define REG_GPIO_DDR _base + 16 + +using namespace uhd; +using namespace usrp; + +class gpio_core_200_impl : public gpio_core_200{ +public: + gpio_core_200_impl(wb_iface::sptr iface, const size_t base, const size_t rb_addr): + _iface(iface), _base(base), _rb_addr(rb_addr) { /* NOP */ } + + void set_pin_ctrl(const unit_t unit, const boost::uint16_t value){ + _pin_ctrl[unit] = value; //shadow + this->update(); //full update + } + + void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value){ + _atr_regs[unit][atr] = value; //shadow + this->update(); //full update + } + + void set_gpio_ddr(const unit_t unit, const boost::uint16_t value){ + _gpio_ddr[unit] = value; //shadow + _iface->poke32(REG_GPIO_DDR, //update the 32 bit register + (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | + (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)) + ); + } + + void set_gpio_out(const unit_t unit, const boost::uint16_t value){ + _gpio_out[unit] = value; //shadow + this->update(); //full update + } + + boost::uint16_t read_gpio(const unit_t unit){ + return boost::uint16_t(_iface->peek32(_rb_addr) >> unit2shit(unit)); + } + +private: + wb_iface::sptr _iface; + const size_t _base; + const size_t _rb_addr; + + uhd::dict<unit_t, boost::uint16_t> _pin_ctrl, _gpio_out, _gpio_ddr; + uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > _atr_regs; + + unsigned unit2shit(const unit_t unit){ + return (unit == dboard_iface::UNIT_RX)? 0 : 16; + } + + void update(void){ + this->update(dboard_iface::ATR_REG_IDLE, REG_GPIO_IDLE); + this->update(dboard_iface::ATR_REG_TX_ONLY, REG_GPIO_TX_ONLY); + this->update(dboard_iface::ATR_REG_RX_ONLY, REG_GPIO_RX_ONLY); + this->update(dboard_iface::ATR_REG_FULL_DUPLEX, REG_GPIO_BOTH); + } + + void update(const atr_reg_t atr, const size_t addr){ + const boost::uint32_t atr_val = + (boost::uint32_t(_atr_regs[dboard_iface::UNIT_RX][atr]) << unit2shit(dboard_iface::UNIT_RX)) | + (boost::uint32_t(_atr_regs[dboard_iface::UNIT_TX][atr]) << unit2shit(dboard_iface::UNIT_TX)); + + const boost::uint32_t gpio_val = + (boost::uint32_t(_gpio_out[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | + (boost::uint32_t(_gpio_out[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)); + + const boost::uint32_t ctrl = + (boost::uint32_t(_pin_ctrl[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | + (boost::uint32_t(_pin_ctrl[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)); + _iface->poke32(addr, (ctrl & atr_val) | ((~ctrl) & gpio_val)); + } + +}; + +gpio_core_200::sptr gpio_core_200::make(wb_iface::sptr iface, const size_t base, const size_t rb_addr){ + return sptr(new gpio_core_200_impl(iface, base, rb_addr)); +} diff --git a/host/lib/usrp/cores/gpio_core_200.hpp b/host/lib/usrp/cores/gpio_core_200.hpp new file mode 100644 index 000000000..278575874 --- /dev/null +++ b/host/lib/usrp/cores/gpio_core_200.hpp @@ -0,0 +1,52 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_GPIO_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_GPIO_CORE_200_HPP + +#include <uhd/config.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <boost/cstdint.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class gpio_core_200 : boost::noncopyable{ +public: + typedef boost::shared_ptr<gpio_core_200> sptr; + + typedef uhd::usrp::dboard_iface::unit_t unit_t; + typedef uhd::usrp::dboard_iface::atr_reg_t atr_reg_t; + + //! makes a new GPIO core from iface and slave base + static sptr make(wb_iface::sptr iface, const size_t base, const size_t rb_addr); + + //! 1 = ATR + virtual void set_pin_ctrl(const unit_t unit, const boost::uint16_t value) = 0; + + virtual void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value) = 0; + + //! 1 = OUTPUT + virtual void set_gpio_ddr(const unit_t unit, const boost::uint16_t value) = 0; + + virtual void set_gpio_out(const unit_t unit, const boost::uint16_t value) = 0; + + virtual boost::uint16_t read_gpio(const unit_t unit) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_GPIO_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/i2c_core_100.cpp b/host/lib/usrp/cores/i2c_core_100.cpp new file mode 100644 index 000000000..ceeb3f518 --- /dev/null +++ b/host/lib/usrp/cores/i2c_core_100.cpp @@ -0,0 +1,140 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "i2c_core_100.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/thread/thread.hpp> //sleep + +#define REG_I2C_PRESCALER_LO _base + 0 +#define REG_I2C_PRESCALER_HI _base + 2 +#define REG_I2C_CTRL _base + 4 +#define REG_I2C_DATA _base + 6 +#define REG_I2C_CMD_STATUS _base + 8 + +// +// STA, STO, RD, WR, and IACK bits are cleared automatically +// + +#define I2C_CTRL_EN (1 << 7) // core enable +#define I2C_CTRL_IE (1 << 6) // interrupt enable + +#define I2C_CMD_START (1 << 7) // generate (repeated) start condition +#define I2C_CMD_STOP (1 << 6) // generate stop condition +#define I2C_CMD_RD (1 << 5) // read from slave +#define I2C_CMD_WR (1 << 4) // write to slave +#define I2C_CMD_NACK (1 << 3) // when a rcvr, send ACK (ACK=0) or NACK (ACK=1) +#define I2C_CMD_RSVD_2 (1 << 2) // reserved +#define I2C_CMD_RSVD_1 (1 << 1) // reserved +#define I2C_CMD_IACK (1 << 0) // set to clear pending interrupt + +#define I2C_ST_RXACK (1 << 7) // Received acknowledgement from slave (1 = NAK, 0 = ACK) +#define I2C_ST_BUSY (1 << 6) // 1 after START signal detected; 0 after STOP signal detected +#define I2C_ST_AL (1 << 5) // Arbitration lost. 1 when core lost arbitration +#define I2C_ST_RSVD_4 (1 << 4) // reserved +#define I2C_ST_RSVD_3 (1 << 3) // reserved +#define I2C_ST_RSVD_2 (1 << 2) // reserved +#define I2C_ST_TIP (1 << 1) // Transfer-in-progress +#define I2C_ST_IP (1 << 0) // Interrupt pending + +using namespace uhd; + +class i2c_core_100_impl : public i2c_core_100{ +public: + i2c_core_100_impl(wb_iface::sptr iface, const size_t base): + _iface(iface), _base(base) + { + //init I2C FPGA interface. + _iface->poke16(REG_I2C_CTRL, 0x0000); + //set prescalers to operate at 400kHz: WB_CLK is 64MHz... + static const boost::uint32_t i2c_datarate = 400000; + static const boost::uint32_t wishbone_clk = 64000000; //FIXME should go somewhere else + boost::uint16_t prescaler = wishbone_clk / (i2c_datarate*5) - 1; + _iface->poke16(REG_I2C_PRESCALER_LO, prescaler & 0xFF); + _iface->poke16(REG_I2C_PRESCALER_HI, (prescaler >> 8) & 0xFF); + _iface->poke16(REG_I2C_CTRL, I2C_CTRL_EN); //enable I2C core + } + + void write_i2c( + boost::uint8_t addr, + const byte_vector_t &bytes + ){ + _iface->poke16(REG_I2C_DATA, (addr << 1) | 0); //addr and read bit (0) + _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | I2C_CMD_START | (bytes.size() == 0 ? I2C_CMD_STOP : 0)); + + //wait for previous transfer to complete + if (not wait_chk_ack()) { + _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_STOP); + return; + } + + for (size_t i = 0; i < bytes.size(); i++) { + _iface->poke16(REG_I2C_DATA, bytes[i]); + _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | ((i == (bytes.size() - 1)) ? I2C_CMD_STOP : 0)); + if(!wait_chk_ack()) { + _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_STOP); + return; + } + } + } + + byte_vector_t read_i2c( + boost::uint8_t addr, + size_t num_bytes + ){ + byte_vector_t bytes; + if (num_bytes == 0) return bytes; + + while (_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_BUSY){ + /* NOP */ + } + + _iface->poke16(REG_I2C_DATA, (addr << 1) | 1); //addr and read bit (1) + _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | I2C_CMD_START); + //wait for previous transfer to complete + if (not wait_chk_ack()) { + _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_STOP); + } + for (size_t i = 0; i < num_bytes; i++) { + _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_RD | ((num_bytes == i+1) ? (I2C_CMD_STOP | I2C_CMD_NACK) : 0)); + i2c_wait(); + bytes.push_back(boost::uint8_t(_iface->peek16(REG_I2C_DATA))); + } + return bytes; + } + +private: + void i2c_wait(void) { + for (size_t i = 0; i < 100; i++){ + if ((_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_TIP) == 0) return; + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + } + UHD_MSG(error) << "i2c_core_100: i2c_wait timeout" << std::endl; + } + + bool wait_chk_ack(void){ + i2c_wait(); + return (_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_RXACK) == 0; + } + + wb_iface::sptr _iface; + const size_t _base; +}; + +i2c_core_100::sptr i2c_core_100::make(wb_iface::sptr iface, const size_t base){ + return sptr(new i2c_core_100_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/i2c_core_100.hpp b/host/lib/usrp/cores/i2c_core_100.hpp new file mode 100644 index 000000000..f7a5ae4f7 --- /dev/null +++ b/host/lib/usrp/cores/i2c_core_100.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_I2C_CORE_100_HPP +#define INCLUDED_LIBUHD_USRP_I2C_CORE_100_HPP + +#include <uhd/config.hpp> +#include <uhd/types/serial.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class i2c_core_100 : boost::noncopyable, public uhd::i2c_iface{ +public: + typedef boost::shared_ptr<i2c_core_100> sptr; + + //! makes a new i2c core from iface and slave base + static sptr make(wb_iface::sptr iface, const size_t base); +}; + +#endif /* INCLUDED_LIBUHD_USRP_I2C_CORE_100_HPP */ diff --git a/host/lib/usrp/cores/rx_dsp_core_200.cpp b/host/lib/usrp/cores/rx_dsp_core_200.cpp new file mode 100644 index 000000000..4b60f111c --- /dev/null +++ b/host/lib/usrp/cores/rx_dsp_core_200.cpp @@ -0,0 +1,262 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "rx_dsp_core_200.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/thread/thread.hpp> //thread sleep +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> +#include <algorithm> +#include <cmath> + +#define REG_DSP_RX_FREQ _dsp_base + 0 +#define REG_DSP_RX_SCALE_IQ _dsp_base + 4 +#define REG_DSP_RX_DECIM _dsp_base + 8 +#define REG_DSP_RX_MUX _dsp_base + 12 + +#define FLAG_DSP_RX_MUX_SWAP_IQ (1 << 0) +#define FLAG_DSP_RX_MUX_REAL_MODE (1 << 1) + +#define REG_RX_CTRL_STREAM_CMD _ctrl_base + 0 +#define REG_RX_CTRL_TIME_HI _ctrl_base + 4 +#define REG_RX_CTRL_TIME_LO _ctrl_base + 8 +#define REG_RX_CTRL_CLEAR _ctrl_base + 12 +#define REG_RX_CTRL_VRT_HDR _ctrl_base + 16 +#define REG_RX_CTRL_VRT_SID _ctrl_base + 20 +#define REG_RX_CTRL_VRT_TLR _ctrl_base + 24 +#define REG_RX_CTRL_NSAMPS_PP _ctrl_base + 28 +#define REG_RX_CTRL_NCHANNELS _ctrl_base + 32 +#define REG_RX_CTRL_FORMAT REG_RX_CTRL_CLEAR //re-use clear address + +template <class T> T ceil_log2(T num){ + return std::ceil(std::log(num)/std::log(T(2))); +} + +using namespace uhd; + +class rx_dsp_core_200_impl : public rx_dsp_core_200{ +public: + rx_dsp_core_200_impl( + wb_iface::sptr iface, + const size_t dsp_base, const size_t ctrl_base, + const boost::uint32_t sid, const bool lingering_packet + ): + _iface(iface), _dsp_base(dsp_base), _ctrl_base(ctrl_base), _sid(sid) + { + //init to something so update method has reasonable defaults + _scaling_adjustment = 1.0; + _dsp_extra_scaling = 1.0; + + //This is a hack/fix for the lingering packet problem. + //The caller should also flush the recv transports + if (lingering_packet){ + stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = 1; + issue_stream_command(stream_cmd); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); //lets lingering pkt propagate + } + + this->clear(); + } + + void clear(void){ + _iface->poke32(REG_RX_CTRL_CLEAR, 1); //reset + _iface->poke32(REG_RX_CTRL_NCHANNELS, 1); + _iface->poke32(REG_RX_CTRL_VRT_HDR, 0 + | (0x1 << 28) //if data with stream id + | (0x1 << 26) //has trailer + | (0x1 << 20) //fractional time sample count + ); + _iface->poke32(REG_RX_CTRL_VRT_SID, _sid); + _iface->poke32(REG_RX_CTRL_VRT_TLR, 0); + } + + void set_nsamps_per_packet(const size_t nsamps){ + _iface->poke32(REG_RX_CTRL_NSAMPS_PP, nsamps); + } + + void issue_stream_command(const stream_cmd_t &stream_cmd){ + UHD_ASSERT_THROW(stream_cmd.num_samps <= 0x0fffffff); + _continuous_streaming = stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS; + + //setup the mode to instruction flags + typedef boost::tuple<bool, bool, bool, bool> inst_t; + static const uhd::dict<stream_cmd_t::stream_mode_t, inst_t> mode_to_inst = boost::assign::map_list_of + //reload, chain, samps, stop + (stream_cmd_t::STREAM_MODE_START_CONTINUOUS, inst_t(true, true, false, false)) + (stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, inst_t(false, false, false, true)) + (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE, inst_t(false, false, true, false)) + (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE, inst_t(false, true, true, false)) + ; + + //setup the instruction flag values + bool inst_reload, inst_chain, inst_samps, inst_stop; + boost::tie(inst_reload, inst_chain, inst_samps, inst_stop) = mode_to_inst[stream_cmd.stream_mode]; + + //calculate the word from flags and length + boost::uint32_t cmd_word = 0; + cmd_word |= boost::uint32_t((stream_cmd.stream_now)? 1 : 0) << 31; + cmd_word |= boost::uint32_t((inst_chain)? 1 : 0) << 30; + cmd_word |= boost::uint32_t((inst_reload)? 1 : 0) << 29; + cmd_word |= boost::uint32_t((inst_stop)? 1 : 0) << 28; + cmd_word |= (inst_samps)? stream_cmd.num_samps : ((inst_stop)? 0 : 1); + + //issue the stream command + _iface->poke32(REG_RX_CTRL_STREAM_CMD, cmd_word); + const boost::uint64_t ticks = (stream_cmd.stream_now)? 0 : stream_cmd.time_spec.to_ticks(_tick_rate); + _iface->poke32(REG_RX_CTRL_TIME_HI, boost::uint32_t(ticks >> 32)); + _iface->poke32(REG_RX_CTRL_TIME_LO, boost::uint32_t(ticks >> 0)); //latches the command + } + + void set_mux(const std::string &mode, const bool fe_swapped){ + static const uhd::dict<std::string, boost::uint32_t> mode_to_mux = boost::assign::map_list_of + ("IQ", 0) + ("QI", FLAG_DSP_RX_MUX_SWAP_IQ) + ("I", FLAG_DSP_RX_MUX_REAL_MODE) + ("Q", FLAG_DSP_RX_MUX_SWAP_IQ | FLAG_DSP_RX_MUX_REAL_MODE) + ; + _iface->poke32(REG_DSP_RX_MUX, mode_to_mux[mode] ^ (fe_swapped? FLAG_DSP_RX_MUX_SWAP_IQ : 0)); + } + + void set_tick_rate(const double rate){ + _tick_rate = rate; + } + + void set_link_rate(const double rate){ + //_link_rate = rate/sizeof(boost::uint32_t); //in samps/s + _link_rate = rate/sizeof(boost::uint16_t); //in samps/s (allows for 8sc) + } + + uhd::meta_range_t get_host_rates(void){ + meta_range_t range; + for (int rate = 512; rate > 256; rate -= 4){ + range.push_back(range_t(_tick_rate/rate)); + } + for (int rate = 256; rate > 128; rate -= 2){ + range.push_back(range_t(_tick_rate/rate)); + } + for (int rate = 128; rate >= int(std::ceil(_tick_rate/_link_rate)); rate -= 1){ + range.push_back(range_t(_tick_rate/rate)); + } + return range; + } + + double set_host_rate(const double rate){ + const size_t decim_rate = boost::math::iround(_tick_rate/this->get_host_rates().clip(rate, true)); + size_t decim = decim_rate; + + //determine which half-band filters are activated + int hb0 = 0, hb1 = 0; + if (decim % 2 == 0){ + hb0 = 1; + decim /= 2; + } + if (decim % 2 == 0){ + hb1 = 1; + decim /= 2; + } + + _iface->poke32(REG_DSP_RX_DECIM, (hb1 << 9) | (hb0 << 8) | (decim & 0xff)); + + // Calculate CIC decimation (i.e., without halfband decimators) + // Calculate closest multiplier constant to reverse gain absent scale multipliers + const double rate_pow = std::pow(double(decim & 0xff), 4); + _scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(1.65*rate_pow); + this->update_scalar(); + + return _tick_rate/decim_rate; + } + + void update_scalar(void){ + const double target_scalar = (1 << 16)*_scaling_adjustment/_dsp_extra_scaling; + const boost::int32_t actual_scalar = boost::math::iround(target_scalar); + _fxpt_scalar_correction = target_scalar/actual_scalar; //should be small + _iface->poke32(REG_DSP_RX_SCALE_IQ, actual_scalar); + } + + double get_scaling_adjustment(void){ + return _fxpt_scalar_correction*_host_extra_scaling/32767.; + } + + double set_freq(const double freq_){ + //correct for outside of rate (wrap around) + double freq = std::fmod(freq_, _tick_rate); + if (std::abs(freq) > _tick_rate/2.0) + freq -= boost::math::sign(freq)*_tick_rate; + + //calculate the freq register word (signed) + UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); + static const double scale_factor = std::pow(2.0, 32); + const boost::int32_t freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); + + //update the actual frequency + const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + + _iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word)); + + return actual_freq; + } + + uhd::meta_range_t get_freq_range(void){ + return uhd::meta_range_t(-_tick_rate/2, +_tick_rate/2, _tick_rate/std::pow(2.0, 32)); + } + + void handle_overflow(void){ + if (_continuous_streaming) issue_stream_command(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + } + + void setup(const uhd::stream_args_t &stream_args){ + if (not stream_args.args.has_key("noclear")) this->clear(); + + unsigned format_word = 0; + if (stream_args.otw_format == "sc16"){ + format_word = 0; + _dsp_extra_scaling = 1.0; + _host_extra_scaling = 1.0; + } + else if (stream_args.otw_format == "sc8"){ + format_word = (1 << 0); + double peak = stream_args.args.cast<double>("peak", 1.0); + peak = std::max(peak, 1.0/256); + _host_extra_scaling = peak*256; + _dsp_extra_scaling = peak*256; + } + else throw uhd::value_error("USRP RX cannot handle requested wire format: " + stream_args.otw_format); + + _host_extra_scaling *= stream_args.args.cast<double>("fullscale", 1.0); + + this->update_scalar(); + + _iface->poke32(REG_RX_CTRL_FORMAT, format_word); + } + +private: + wb_iface::sptr _iface; + const size_t _dsp_base, _ctrl_base; + double _tick_rate, _link_rate; + bool _continuous_streaming; + double _scaling_adjustment, _dsp_extra_scaling, _host_extra_scaling, _fxpt_scalar_correction; + const boost::uint32_t _sid; +}; + +rx_dsp_core_200::sptr rx_dsp_core_200::make(wb_iface::sptr iface, const size_t dsp_base, const size_t ctrl_base, const boost::uint32_t sid, const bool lingering_packet){ + return sptr(new rx_dsp_core_200_impl(iface, dsp_base, ctrl_base, sid, lingering_packet)); +} diff --git a/host/lib/usrp/cores/rx_dsp_core_200.hpp b/host/lib/usrp/cores/rx_dsp_core_200.hpp new file mode 100644 index 000000000..b01f751e9 --- /dev/null +++ b/host/lib/usrp/cores/rx_dsp_core_200.hpp @@ -0,0 +1,67 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_RX_DSP_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_RX_DSP_CORE_200_HPP + +#include <uhd/config.hpp> +#include <uhd/stream.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/stream_cmd.hpp> +#include "wb_iface.hpp" +#include <string> + +class rx_dsp_core_200 : boost::noncopyable{ +public: + typedef boost::shared_ptr<rx_dsp_core_200> sptr; + + static sptr make( + wb_iface::sptr iface, + const size_t dsp_base, const size_t ctrl_base, + const boost::uint32_t sid, const bool lingering_packet = false + ); + + virtual void clear(void) = 0; + + virtual void set_nsamps_per_packet(const size_t nsamps) = 0; + + virtual void issue_stream_command(const uhd::stream_cmd_t &stream_cmd) = 0; + + virtual void set_mux(const std::string &mode, const bool fe_swapped = false) = 0; + + virtual void set_tick_rate(const double rate) = 0; + + virtual void set_link_rate(const double rate) = 0; + + virtual double set_host_rate(const double rate) = 0; + + virtual uhd::meta_range_t get_host_rates(void) = 0; + + virtual double get_scaling_adjustment(void) = 0; + + virtual uhd::meta_range_t get_freq_range(void) = 0; + + virtual double set_freq(const double freq) = 0; + + virtual void handle_overflow(void) = 0; + + virtual void setup(const uhd::stream_args_t &stream_args) = 0; +}; + +#endif /* INCLUDED_LIBUHD_USRP_RX_DSP_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/rx_frontend_core_200.cpp b/host/lib/usrp/cores/rx_frontend_core_200.cpp new file mode 100644 index 000000000..d6396ef45 --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_200.cpp @@ -0,0 +1,79 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "rx_frontend_core_200.hpp" +#include <boost/math/special_functions/round.hpp> + +#define REG_RX_FE_SWAP_IQ _base + 0 //lower bit +#define REG_RX_FE_MAG_CORRECTION _base + 4 //18 bits +#define REG_RX_FE_PHASE_CORRECTION _base + 8 //18 bits +#define REG_RX_FE_OFFSET_I _base + 12 //18 bits +#define REG_RX_FE_OFFSET_Q _base + 16 //18 bits + +#define OFFSET_FIXED (1ul << 31) +#define OFFSET_SET (1ul << 30) + +static boost::uint32_t fs_to_bits(const double num, const size_t bits){ + return boost::int32_t(boost::math::round(num * (1 << (bits-1)))); +} + + +class rx_frontend_core_200_impl : public rx_frontend_core_200{ +public: + rx_frontend_core_200_impl(wb_iface::sptr iface, const size_t base): + _iface(iface), _base(base) + { + //NOP + } + + void set_mux(const bool swap){ + _iface->poke32(REG_RX_FE_SWAP_IQ, swap? 1 : 0); + } + + void set_dc_offset_auto(const bool enb){ + this->set_dc_offset(enb? 0 : OFFSET_FIXED); + } + + std::complex<double> set_dc_offset(const std::complex<double> &off){ + static const double scaler = double(1ul << 29); + _i_dc_off = boost::math::iround(off.real()*scaler); + _q_dc_off = boost::math::iround(off.imag()*scaler); + + this->set_dc_offset(OFFSET_SET | OFFSET_FIXED); + + return std::complex<double>(_i_dc_off/scaler, _q_dc_off/scaler); + } + + void set_dc_offset(const boost::uint32_t flags){ + _iface->poke32(REG_RX_FE_OFFSET_I, flags | _i_dc_off); + _iface->poke32(REG_RX_FE_OFFSET_Q, flags | _q_dc_off); + } + + void set_iq_balance(const std::complex<double> &cor){ + _iface->poke32(REG_RX_FE_MAG_CORRECTION, fs_to_bits(std::abs(cor) - 1, 18)); + _iface->poke32(REG_RX_FE_PHASE_CORRECTION, fs_to_bits(std::arg(cor)/6.28318531, 18)); + } + +private: + boost::int32_t _i_dc_off, _q_dc_off; + wb_iface::sptr _iface; + const size_t _base; +}; + +rx_frontend_core_200::sptr rx_frontend_core_200::make(wb_iface::sptr iface, const size_t base){ + return sptr(new rx_frontend_core_200_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/rx_frontend_core_200.hpp b/host/lib/usrp/cores/rx_frontend_core_200.hpp new file mode 100644 index 000000000..5755424c8 --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_200.hpp @@ -0,0 +1,44 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_200_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" +#include <complex> +#include <string> + +class rx_frontend_core_200 : boost::noncopyable{ +public: + typedef boost::shared_ptr<rx_frontend_core_200> sptr; + + static sptr make(wb_iface::sptr iface, const size_t base); + + virtual void set_mux(const bool swap) = 0; + + virtual void set_dc_offset_auto(const bool enb) = 0; + + virtual std::complex<double> set_dc_offset(const std::complex<double> &off) = 0; + + virtual void set_iq_balance(const std::complex<double> &cor) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/spi_core_100.cpp b/host/lib/usrp/cores/spi_core_100.cpp new file mode 100644 index 000000000..d11a499a9 --- /dev/null +++ b/host/lib/usrp/cores/spi_core_100.cpp @@ -0,0 +1,88 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "spi_core_100.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/thread/thread.hpp> //sleep + +#define REG_SPI_TXRX0 _base + 0 +#define REG_SPI_TXRX1 _base + 4 +#define REG_SPI_TXRX2 _base + 8 +#define REG_SPI_TXRX3 _base + 12 +#define REG_SPI_CTRL _base + 16 +#define REG_SPI_DIV _base + 20 +#define REG_SPI_SS _base + 24 + +//spi ctrl register bit definitions +#define SPI_CTRL_ASS (1<<13) +#define SPI_CTRL_IE (1<<12) +#define SPI_CTRL_LSB (1<<11) +#define SPI_CTRL_TXNEG (1<<10) //mosi edge, push on falling edge when 1 +#define SPI_CTRL_RXNEG (1<< 9) //miso edge, latch on falling edge when 1 +#define SPI_CTRL_GO_BSY (1<< 8) +#define SPI_CTRL_CHAR_LEN_MASK 0x7F + +using namespace uhd; + +class spi_core_100_impl : public spi_core_100{ +public: + spi_core_100_impl(wb_iface::sptr iface, const size_t base): + _iface(iface), _base(base) { /* NOP */} + + boost::uint32_t transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback + ){ + UHD_ASSERT_THROW(num_bits <= 32 and (num_bits % 8) == 0); + + int edge_flags = ((config.miso_edge==spi_config_t::EDGE_FALL) ? SPI_CTRL_RXNEG : 0) | + ((config.mosi_edge==spi_config_t::EDGE_FALL) ? 0 : SPI_CTRL_TXNEG) + ; + boost::uint16_t ctrl = SPI_CTRL_ASS | (SPI_CTRL_CHAR_LEN_MASK & num_bits) | edge_flags; + + spi_wait(); + _iface->poke16(REG_SPI_DIV, 0x0001); // = fpga_clk / 4 + _iface->poke32(REG_SPI_SS, which_slave & 0xFFFF); + _iface->poke32(REG_SPI_TXRX0, data); + _iface->poke16(REG_SPI_CTRL, ctrl); + _iface->poke16(REG_SPI_CTRL, ctrl | SPI_CTRL_GO_BSY); + + if (not readback) return 0; + spi_wait(); + return _iface->peek32(REG_SPI_TXRX0); + } + +private: + void spi_wait(void) { + for (size_t i = 0; i < 100; i++){ + if ((_iface->peek16(REG_SPI_CTRL) & SPI_CTRL_GO_BSY) == 0) return; + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + } + UHD_MSG(error) << "spi_core_100: spi_wait timeout" << std::endl; + } + + wb_iface::sptr _iface; + const size_t _base; +}; + +spi_core_100::sptr spi_core_100::make(wb_iface::sptr iface, const size_t base){ + return sptr(new spi_core_100_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/spi_core_100.hpp b/host/lib/usrp/cores/spi_core_100.hpp new file mode 100644 index 000000000..87d328aaa --- /dev/null +++ b/host/lib/usrp/cores/spi_core_100.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_SPI_CORE_100_HPP +#define INCLUDED_LIBUHD_USRP_SPI_CORE_100_HPP + +#include <uhd/config.hpp> +#include <uhd/types/serial.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class spi_core_100 : boost::noncopyable, public uhd::spi_iface{ +public: + typedef boost::shared_ptr<spi_core_100> sptr; + + //! makes a new spi core from iface and slave base + static sptr make(wb_iface::sptr iface, const size_t base); +}; + +#endif /* INCLUDED_LIBUHD_USRP_SPI_CORE_100_HPP */ diff --git a/host/lib/usrp/cores/time64_core_200.cpp b/host/lib/usrp/cores/time64_core_200.cpp new file mode 100644 index 000000000..e460d1106 --- /dev/null +++ b/host/lib/usrp/cores/time64_core_200.cpp @@ -0,0 +1,134 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "time64_core_200.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/math/special_functions/round.hpp> + +#define REG_TIME64_TICKS_HI _base + 0 +#define REG_TIME64_TICKS_LO _base + 4 +#define REG_TIME64_FLAGS _base + 8 +#define REG_TIME64_IMM _base + 12 +#define REG_TIME64_MIMO_SYNC _base + 20 //lower byte is delay cycles + +//pps flags (see above) +#define FLAG_TIME64_PPS_NEGEDGE (0 << 0) +#define FLAG_TIME64_PPS_POSEDGE (1 << 0) +#define FLAG_TIME64_PPS_SMA (0 << 1) +#define FLAG_TIME64_PPS_MIMO (1 << 1) //apparently not used + +#define FLAG_TIME64_LATCH_NOW 1 +#define FLAG_TIME64_LATCH_NEXT_PPS 0 + +#define FLAG_TIME64_MIMO_SYNC (1 << 8) + +using namespace uhd; + +class time64_core_200_impl : public time64_core_200{ +public: + time64_core_200_impl( + wb_iface::sptr iface, const size_t base, + const readback_bases_type &readback_bases, + const size_t mimo_delay_cycles + ): + _iface(iface), _base(base), + _readback_bases(readback_bases), + _mimo_delay_cycles(mimo_delay_cycles) + { + _sources.push_back("none"); + _sources.push_back("external"); + _sources.push_back("_external_"); + if (_mimo_delay_cycles != 0) _sources.push_back("mimo"); + } + + void set_tick_rate(const double rate){ + _tick_rate = rate; + } + + uhd::time_spec_t get_time_now(void){ + for (size_t i = 0; i < 3; i++){ //special algorithm because we cant read 64 bits synchronously + const boost::uint32_t ticks_hi = _iface->peek32(_readback_bases.rb_hi_now); + const boost::uint32_t ticks_lo = _iface->peek32(_readback_bases.rb_lo_now); + if (ticks_hi != _iface->peek32(_readback_bases.rb_hi_now)) continue; + const boost::uint64_t ticks = (boost::uint64_t(ticks_hi) << 32) | ticks_lo; + return time_spec_t::from_ticks(ticks, _tick_rate); + } + throw uhd::runtime_error("time64_core_200: get time now timeout"); + } + + uhd::time_spec_t get_time_last_pps(void){ + for (size_t i = 0; i < 3; i++){ //special algorithm because we cant read 64 bits synchronously + const boost::uint32_t ticks_hi = _iface->peek32(_readback_bases.rb_hi_pps); + const boost::uint32_t ticks_lo = _iface->peek32(_readback_bases.rb_lo_pps); + if (ticks_hi != _iface->peek32(_readback_bases.rb_hi_pps)) continue; + const boost::uint64_t ticks = (boost::uint64_t(ticks_hi) << 32) | ticks_lo; + return time_spec_t::from_ticks(ticks, _tick_rate); + } + throw uhd::runtime_error("time64_core_200: get time last pps timeout"); + } + + void set_time_now(const uhd::time_spec_t &time){ + const boost::uint64_t ticks = time.to_ticks(_tick_rate); + _iface->poke32(REG_TIME64_TICKS_LO, boost::uint32_t(ticks >> 0)); + _iface->poke32(REG_TIME64_IMM, FLAG_TIME64_LATCH_NOW); + _iface->poke32(REG_TIME64_TICKS_HI, boost::uint32_t(ticks >> 32)); //latches all 3 + } + + void set_time_next_pps(const uhd::time_spec_t &time){ + const boost::uint64_t ticks = time.to_ticks(_tick_rate); + _iface->poke32(REG_TIME64_TICKS_LO, boost::uint32_t(ticks >> 0)); + _iface->poke32(REG_TIME64_IMM, FLAG_TIME64_LATCH_NEXT_PPS); + _iface->poke32(REG_TIME64_TICKS_HI, boost::uint32_t(ticks >> 32)); //latches all 3 + } + + void set_time_source(const std::string &source){ + assert_has(_sources, source, "time source"); + + //setup pps flags + if (source == "external"){ + _iface->poke32(REG_TIME64_FLAGS, FLAG_TIME64_PPS_SMA | FLAG_TIME64_PPS_POSEDGE); + } + else if (source == "_external_"){ + _iface->poke32(REG_TIME64_FLAGS, FLAG_TIME64_PPS_SMA | FLAG_TIME64_PPS_NEGEDGE); + } + + //setup mimo flags + if (source == "mimo"){ + _iface->poke32(REG_TIME64_MIMO_SYNC, FLAG_TIME64_MIMO_SYNC | (_mimo_delay_cycles & 0xff)); + } + else{ + _iface->poke32(REG_TIME64_MIMO_SYNC, 0); + } + } + + std::vector<std::string> get_time_sources(void){ + return _sources; + } + +private: + wb_iface::sptr _iface; + const size_t _base; + const readback_bases_type _readback_bases; + double _tick_rate; + const size_t _mimo_delay_cycles; + std::vector<std::string> _sources; +}; + +time64_core_200::sptr time64_core_200::make(wb_iface::sptr iface, const size_t base, const readback_bases_type &readback_bases, const size_t mimo_delay_cycles){ + return sptr(new time64_core_200_impl(iface, base, readback_bases, mimo_delay_cycles)); +} diff --git a/host/lib/usrp/cores/time64_core_200.hpp b/host/lib/usrp/cores/time64_core_200.hpp new file mode 100644 index 000000000..7571573a5 --- /dev/null +++ b/host/lib/usrp/cores/time64_core_200.hpp @@ -0,0 +1,61 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TIME64_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_TIME64_CORE_200_HPP + +#include <uhd/config.hpp> +#include <uhd/types/time_spec.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" +#include <string> +#include <vector> + +class time64_core_200 : boost::noncopyable{ +public: + typedef boost::shared_ptr<time64_core_200> sptr; + + struct readback_bases_type{ + size_t rb_hi_now, rb_lo_now; + size_t rb_hi_pps, rb_lo_pps; + }; + + //! makes a new time64 core from iface and slave base + static sptr make( + wb_iface::sptr iface, const size_t base, + const readback_bases_type &readback_bases, + const size_t mimo_delay_cycles = 0 // 0 means no-mimo + ); + + virtual void set_tick_rate(const double rate) = 0; + + virtual uhd::time_spec_t get_time_now(void) = 0; + + virtual uhd::time_spec_t get_time_last_pps(void) = 0; + + virtual void set_time_now(const uhd::time_spec_t &time) = 0; + + virtual void set_time_next_pps(const uhd::time_spec_t &time) = 0; + + virtual void set_time_source(const std::string &source) = 0; + + virtual std::vector<std::string> get_time_sources(void) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TIME64_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/tx_dsp_core_200.cpp b/host/lib/usrp/cores/tx_dsp_core_200.cpp new file mode 100644 index 000000000..2faf7c28b --- /dev/null +++ b/host/lib/usrp/cores/tx_dsp_core_200.cpp @@ -0,0 +1,216 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "tx_dsp_core_200.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <algorithm> +#include <cmath> + +#define REG_DSP_TX_FREQ _dsp_base + 0 +#define REG_DSP_TX_SCALE_IQ _dsp_base + 4 +#define REG_DSP_TX_INTERP _dsp_base + 8 + +#define REG_TX_CTRL_CLEAR _ctrl_base + 0 +#define REG_TX_CTRL_FORMAT _ctrl_base + 4 +#define REG_TX_CTRL_REPORT_SID _ctrl_base + 8 +#define REG_TX_CTRL_POLICY _ctrl_base + 12 +#define REG_TX_CTRL_CYCLES_PER_UP _ctrl_base + 16 +#define REG_TX_CTRL_PACKETS_PER_UP _ctrl_base + 20 + +#define FLAG_TX_CTRL_POLICY_WAIT (0x1 << 0) +#define FLAG_TX_CTRL_POLICY_NEXT_PACKET (0x1 << 1) +#define FLAG_TX_CTRL_POLICY_NEXT_BURST (0x1 << 2) + +//enable flag for registers: cycles and packets per update packet +#define FLAG_TX_CTRL_UP_ENB (1ul << 31) + +template <class T> T ceil_log2(T num){ + return std::ceil(std::log(num)/std::log(T(2))); +} + +using namespace uhd; + +class tx_dsp_core_200_impl : public tx_dsp_core_200{ +public: + tx_dsp_core_200_impl( + wb_iface::sptr iface, + const size_t dsp_base, const size_t ctrl_base, + const boost::uint32_t sid + ): + _iface(iface), _dsp_base(dsp_base), _ctrl_base(ctrl_base), _sid(sid) + { + //init to something so update method has reasonable defaults + _scaling_adjustment = 1.0; + _dsp_extra_scaling = 1.0; + + //init the tx control registers + this->clear(); + this->set_underflow_policy("next_packet"); + } + + void clear(void){ + _iface->poke32(REG_TX_CTRL_CLEAR, 1); //reset and flush technique + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + _iface->poke32(REG_TX_CTRL_CLEAR, 0); + _iface->poke32(REG_TX_CTRL_REPORT_SID, _sid); + } + + void set_underflow_policy(const std::string &policy){ + if (policy == "next_packet"){ + _iface->poke32(REG_TX_CTRL_POLICY, FLAG_TX_CTRL_POLICY_NEXT_PACKET); + } + else if (policy == "next_burst"){ + _iface->poke32(REG_TX_CTRL_POLICY, FLAG_TX_CTRL_POLICY_NEXT_BURST); + } + else throw uhd::value_error("USRP TX cannot handle requested underflow policy: " + policy); + } + + void set_tick_rate(const double rate){ + _tick_rate = rate; + } + + void set_link_rate(const double rate){ + //_link_rate = rate/sizeof(boost::uint32_t); //in samps/s + _link_rate = rate/sizeof(boost::uint16_t); //in samps/s (allows for 8sc) + } + + uhd::meta_range_t get_host_rates(void){ + meta_range_t range; + for (int rate = 512; rate > 256; rate -= 4){ + range.push_back(range_t(_tick_rate/rate)); + } + for (int rate = 256; rate > 128; rate -= 2){ + range.push_back(range_t(_tick_rate/rate)); + } + for (int rate = 128; rate >= int(std::ceil(_tick_rate/_link_rate)); rate -= 1){ + range.push_back(range_t(_tick_rate/rate)); + } + return range; + } + + double set_host_rate(const double rate){ + const size_t interp_rate = boost::math::iround(_tick_rate/this->get_host_rates().clip(rate, true)); + size_t interp = interp_rate; + + //determine which half-band filters are activated + int hb0 = 0, hb1 = 0; + if (interp % 2 == 0){ + hb0 = 1; + interp /= 2; + } + if (interp % 2 == 0){ + hb1 = 1; + interp /= 2; + } + + _iface->poke32(REG_DSP_TX_INTERP, (hb1 << 9) | (hb0 << 8) | (interp & 0xff)); + + // Calculate CIC interpolation (i.e., without halfband interpolators) + // Calculate closest multiplier constant to reverse gain absent scale multipliers + const double rate_pow = std::pow(double(interp & 0xff), 3); + _scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(1.65*rate_pow); + this->update_scalar(); + + return _tick_rate/interp_rate; + } + + void update_scalar(void){ + const double target_scalar = (1 << 17)*_scaling_adjustment/_dsp_extra_scaling; + const boost::int32_t actual_scalar = boost::math::iround(target_scalar); + _fxpt_scalar_correction = target_scalar/actual_scalar; //should be small + _iface->poke32(REG_DSP_TX_SCALE_IQ, actual_scalar); + } + + double get_scaling_adjustment(void){ + return _fxpt_scalar_correction*_host_extra_scaling*32767.; + } + + double set_freq(const double freq_){ + //correct for outside of rate (wrap around) + double freq = std::fmod(freq_, _tick_rate); + if (std::abs(freq) > _tick_rate/2.0) + freq -= boost::math::sign(freq)*_tick_rate; + + //calculate the freq register word (signed) + UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); + static const double scale_factor = std::pow(2.0, 32); + const boost::int32_t freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); + + //update the actual frequency + const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + + _iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word)); + + return actual_freq; + } + + uhd::meta_range_t get_freq_range(void){ + return uhd::meta_range_t(-_tick_rate/2, +_tick_rate/2, _tick_rate/std::pow(2.0, 32)); + } + + void set_updates(const size_t cycles_per_up, const size_t packets_per_up){ + _iface->poke32(REG_TX_CTRL_CYCLES_PER_UP, (cycles_per_up == 0)? 0 : (FLAG_TX_CTRL_UP_ENB | cycles_per_up)); + _iface->poke32(REG_TX_CTRL_PACKETS_PER_UP, (packets_per_up == 0)? 0 : (FLAG_TX_CTRL_UP_ENB | packets_per_up)); + } + + void setup(const uhd::stream_args_t &stream_args){ + if (not stream_args.args.has_key("noclear")) this->clear(); + + unsigned format_word = 0; + if (stream_args.otw_format == "sc16"){ + format_word = 0; + _dsp_extra_scaling = 1.0; + _host_extra_scaling = 1.0; + } + else if (stream_args.otw_format == "sc8"){ + format_word = (1 << 0); + double peak = stream_args.args.cast<double>("peak", 1.0); + peak = std::max(peak, 1.0/256); + _host_extra_scaling = 1.0/peak/256; + _dsp_extra_scaling = 1.0/peak; + } + else throw uhd::value_error("USRP TX cannot handle requested wire format: " + stream_args.otw_format); + + _host_extra_scaling /= stream_args.args.cast<double>("fullscale", 1.0); + + this->update_scalar(); + + _iface->poke32(REG_TX_CTRL_FORMAT, format_word); + + if (stream_args.args.has_key("underflow_policy")){ + this->set_underflow_policy(stream_args.args["underflow_policy"]); + } + } + +private: + wb_iface::sptr _iface; + const size_t _dsp_base, _ctrl_base; + double _tick_rate, _link_rate; + double _scaling_adjustment, _dsp_extra_scaling, _host_extra_scaling, _fxpt_scalar_correction; + const boost::uint32_t _sid; +}; + +tx_dsp_core_200::sptr tx_dsp_core_200::make(wb_iface::sptr iface, const size_t dsp_base, const size_t ctrl_base, const boost::uint32_t sid){ + return sptr(new tx_dsp_core_200_impl(iface, dsp_base, ctrl_base, sid)); +} diff --git a/host/lib/usrp/cores/tx_dsp_core_200.hpp b/host/lib/usrp/cores/tx_dsp_core_200.hpp new file mode 100644 index 000000000..0e1cfb6bc --- /dev/null +++ b/host/lib/usrp/cores/tx_dsp_core_200.hpp @@ -0,0 +1,59 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TX_DSP_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_TX_DSP_CORE_200_HPP + +#include <uhd/config.hpp> +#include <uhd/stream.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class tx_dsp_core_200 : boost::noncopyable{ +public: + typedef boost::shared_ptr<tx_dsp_core_200> sptr; + + static sptr make( + wb_iface::sptr iface, + const size_t dsp_base, const size_t ctrl_base, + const boost::uint32_t sid + ); + + virtual void clear(void) = 0; + + virtual void set_tick_rate(const double rate) = 0; + + virtual void set_link_rate(const double rate) = 0; + + virtual double set_host_rate(const double rate) = 0; + + virtual uhd::meta_range_t get_host_rates(void) = 0; + + virtual double get_scaling_adjustment(void) = 0; + + virtual uhd::meta_range_t get_freq_range(void) = 0; + + virtual double set_freq(const double freq) = 0; + + virtual void set_updates(const size_t cycles_per_up, const size_t packets_per_up) = 0; + + virtual void setup(const uhd::stream_args_t &stream_args) = 0; +}; + +#endif /* INCLUDED_LIBUHD_USRP_TX_DSP_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/tx_frontend_core_200.cpp b/host/lib/usrp/cores/tx_frontend_core_200.cpp new file mode 100644 index 000000000..b90281d9f --- /dev/null +++ b/host/lib/usrp/cores/tx_frontend_core_200.cpp @@ -0,0 +1,76 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "tx_frontend_core_200.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/math/special_functions/round.hpp> + +#define REG_TX_FE_DC_OFFSET_I _base + 0 //24 bits +#define REG_TX_FE_DC_OFFSET_Q _base + 4 //24 bits +#define REG_TX_FE_MAG_CORRECTION _base + 8 //18 bits +#define REG_TX_FE_PHASE_CORRECTION _base + 12 //18 bits +#define REG_TX_FE_MUX _base + 16 //8 bits (std output = 0x10, reversed = 0x01) + +static boost::uint32_t fs_to_bits(const double num, const size_t bits){ + return boost::int32_t(boost::math::round(num * (1 << (bits-1)))); +} + + +class tx_frontend_core_200_impl : public tx_frontend_core_200{ +public: + tx_frontend_core_200_impl(wb_iface::sptr iface, const size_t base): + _iface(iface), _base(base) + { + //NOP + } + + void set_mux(const std::string &mode){ + static const uhd::dict<std::string, boost::uint32_t> mode_to_mux = boost::assign::map_list_of + ("IQ", (0x1 << 4) | (0x0 << 0)) //DAC0Q=DUC0Q, DAC0I=DUC0I + ("QI", (0x0 << 4) | (0x1 << 0)) //DAC0Q=DUC0I, DAC0I=DUC0Q + ("I", (0xf << 4) | (0x0 << 0)) //DAC0Q=ZERO, DAC0I=DUC0I + ("Q", (0x0 << 4) | (0xf << 0)) //DAC0Q=DUC0I, DAC0I=ZERO + ; + _iface->poke32(REG_TX_FE_MUX, mode_to_mux[mode]); + } + + std::complex<double> set_dc_offset(const std::complex<double> &off){ + static const double scaler = double(1ul << 23); + const boost::int32_t i_dc_off = boost::math::iround(off.real()*scaler); + const boost::int32_t q_dc_off = boost::math::iround(off.imag()*scaler); + + _iface->poke32(REG_TX_FE_DC_OFFSET_I, i_dc_off); + _iface->poke32(REG_TX_FE_DC_OFFSET_Q, q_dc_off); + + return std::complex<double>(i_dc_off/scaler, q_dc_off/scaler); + } + + void set_iq_balance(const std::complex<double> &cor){ + _iface->poke32(REG_TX_FE_MAG_CORRECTION, fs_to_bits(std::abs(cor) - 1, 18)); + _iface->poke32(REG_TX_FE_PHASE_CORRECTION, fs_to_bits(std::arg(cor)/6.28318531, 18)); + } + +private: + wb_iface::sptr _iface; + const size_t _base; +}; + +tx_frontend_core_200::sptr tx_frontend_core_200::make(wb_iface::sptr iface, const size_t base){ + return sptr(new tx_frontend_core_200_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/tx_frontend_core_200.hpp b/host/lib/usrp/cores/tx_frontend_core_200.hpp new file mode 100644 index 000000000..8ee0f3e6d --- /dev/null +++ b/host/lib/usrp/cores/tx_frontend_core_200.hpp @@ -0,0 +1,42 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_RX_FRONTEND_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_RX_FRONTEND_CORE_200_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" +#include <complex> +#include <string> + +class tx_frontend_core_200 : boost::noncopyable{ +public: + typedef boost::shared_ptr<tx_frontend_core_200> sptr; + + static sptr make(wb_iface::sptr iface, const size_t base); + + virtual void set_mux(const std::string &mode) = 0; + + virtual std::complex<double> set_dc_offset(const std::complex<double> &off) = 0; + + virtual void set_iq_balance(const std::complex<double> &cor) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_RX_FRONTEND_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/user_settings_core_200.cpp b/host/lib/usrp/cores/user_settings_core_200.cpp new file mode 100644 index 000000000..d262631b1 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_200.cpp @@ -0,0 +1,43 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "user_settings_core_200.hpp" + +#define REG_USER_ADDR _base + 0 +#define REG_USER_DATA _base + 4 + +class user_settings_core_200_impl : public user_settings_core_200{ +public: + user_settings_core_200_impl(wb_iface::sptr iface, const size_t base): + _iface(iface), _base(base) + { + //NOP + } + + void set_reg(const user_reg_t ®){ + _iface->poke32(REG_USER_ADDR, reg.first); + _iface->poke32(REG_USER_DATA, reg.second); + } + +private: + wb_iface::sptr _iface; + const size_t _base; +}; + +user_settings_core_200::sptr user_settings_core_200::make(wb_iface::sptr iface, const size_t base){ + return sptr(new user_settings_core_200_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/user_settings_core_200.hpp b/host/lib/usrp/cores/user_settings_core_200.hpp new file mode 100644 index 000000000..1f5d13de7 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_200.hpp @@ -0,0 +1,36 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_200_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class user_settings_core_200 : boost::noncopyable{ +public: + typedef boost::shared_ptr<user_settings_core_200> sptr; + typedef std::pair<boost::uint8_t, boost::uint32_t> user_reg_t; + + static sptr make(wb_iface::sptr iface, const size_t base); + + virtual void set_reg(const user_reg_t ®) = 0; +}; + +#endif /* INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/wb_iface.hpp b/host/lib/usrp/cores/wb_iface.hpp new file mode 100644 index 000000000..982594b21 --- /dev/null +++ b/host/lib/usrp/cores/wb_iface.hpp @@ -0,0 +1,60 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_WB_IFACE_HPP +#define INCLUDED_LIBUHD_USRP_WB_IFACE_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> + +class wb_iface{ +public: + typedef boost::shared_ptr<wb_iface> sptr; + typedef boost::uint32_t wb_addr_type; + + /*! + * Write a register (32 bits) + * \param addr the address + * \param data the 32bit data + */ + virtual void poke32(wb_addr_type addr, boost::uint32_t data) = 0; + + /*! + * Read a register (32 bits) + * \param addr the address + * \return the 32bit data + */ + virtual boost::uint32_t peek32(wb_addr_type addr) = 0; + + /*! + * Write a register (16 bits) + * \param addr the address + * \param data the 16bit data + */ + virtual void poke16(wb_addr_type addr, boost::uint16_t data) = 0; + + /*! + * Read a register (16 bits) + * \param addr the address + * \return the 16bit data + */ + virtual boost::uint16_t peek16(wb_addr_type addr) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_WB_IFACE_HPP */ diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt new file mode 100644 index 000000000..b000c7f33 --- /dev/null +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/db_basic_and_lf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_rfx.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_xcvr2450.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_common.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_version3.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_version4.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_common.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version3.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version4.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_simple.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_unknown.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.cpp +) + diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp new file mode 100644 index 000000000..fc42a73d5 --- /dev/null +++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp @@ -0,0 +1,197 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +//! provider function for the always zero freq +static double always_zero_freq(void){return 0.0;} + +/*********************************************************************** + * Constants + **********************************************************************/ +static const uhd::dict<std::string, double> subdev_bandwidth_scalar = map_list_of + ("A", 1.0) + ("B", 1.0) + ("AB", 2.0) + ("BA", 2.0) +; + +/*********************************************************************** + * The basic and lf boards: + * They share a common class because only the frequency bounds differ. + **********************************************************************/ +class basic_rx : public rx_dboard_base{ +public: + basic_rx(ctor_args_t args, double max_freq); + ~basic_rx(void); + +private: + double _max_freq; +}; + +class basic_tx : public tx_dboard_base{ +public: + basic_tx(ctor_args_t args, double max_freq); + ~basic_tx(void); + +private: + double _max_freq; +}; + +static const uhd::dict<std::string, std::string> sd_name_to_conn = map_list_of + ("AB", "IQ") + ("BA", "QI") + ("A", "I") + ("B", "Q") +; + +/*********************************************************************** + * Register the basic and LF dboards + **********************************************************************/ +static dboard_base::sptr make_basic_rx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new basic_rx(args, 250e6)); +} + +static dboard_base::sptr make_basic_tx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new basic_tx(args, 250e6)); +} + +static dboard_base::sptr make_lf_rx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new basic_rx(args, 32e6)); +} + +static dboard_base::sptr make_lf_tx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new basic_tx(args, 32e6)); +} + +UHD_STATIC_BLOCK(reg_basic_and_lf_dboards){ + dboard_manager::register_dboard(0x0000, &make_basic_tx, "Basic TX", sd_name_to_conn.keys()); + dboard_manager::register_dboard(0x0001, &make_basic_rx, "Basic RX", sd_name_to_conn.keys()); + dboard_manager::register_dboard(0x000e, &make_lf_tx, "LF TX", sd_name_to_conn.keys()); + dboard_manager::register_dboard(0x000f, &make_lf_rx, "LF RX", sd_name_to_conn.keys()); +} + +/*********************************************************************** + * Basic and LF RX dboard + **********************************************************************/ +basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){ + _max_freq = max_freq; + //this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + + //////////////////////////////////////////////////////////////////// + // Register properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name").set( + std::string(str(boost::format("%s - %s") + % get_rx_id().to_pp_string() + % get_subdev_name() + ))); + this->get_rx_subtree()->create<int>("gains"); //phony property so this dir exists + this->get_rx_subtree()->create<double>("freq/value") + .publish(&always_zero_freq); + this->get_rx_subtree()->create<meta_range_t>("freq/range") + .set(freq_range_t(-_max_freq, +_max_freq)); + this->get_rx_subtree()->create<std::string>("antenna/value") + .set(""); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(list_of("")); + this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists + this->get_rx_subtree()->create<std::string>("connection") + .set(sd_name_to_conn[get_subdev_name()]); + this->get_rx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + this->get_rx_subtree()->create<double>("bandwidth/value") + .set(subdev_bandwidth_scalar[get_subdev_name()]*_max_freq); + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(subdev_bandwidth_scalar[get_subdev_name()]*_max_freq, subdev_bandwidth_scalar[get_subdev_name()]*_max_freq)); + + //disable RX dboard clock by default + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, false); + + //set GPIOs to output 0x0000 to decrease noise pickup + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0000); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0xFFFF); + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0x0000); +} + +basic_rx::~basic_rx(void){ + /* NOP */ +} + +/*********************************************************************** + * Basic and LF TX dboard + **********************************************************************/ +basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){ + _max_freq = max_freq; + //this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); + + //////////////////////////////////////////////////////////////////// + // Register properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<std::string>("name").set( + std::string(str(boost::format("%s - %s") + % get_tx_id().to_pp_string() + % get_subdev_name() + ))); + this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists + this->get_tx_subtree()->create<double>("freq/value") + .publish(&always_zero_freq); + this->get_tx_subtree()->create<meta_range_t>("freq/range") + .set(freq_range_t(-_max_freq, +_max_freq)); + this->get_tx_subtree()->create<std::string>("antenna/value") + .set(""); + this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(list_of("")); + this->get_tx_subtree()->create<int>("sensors"); //phony property so this dir exists + this->get_tx_subtree()->create<std::string>("connection") + .set(sd_name_to_conn[get_subdev_name()]); + this->get_tx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_tx_subtree()->create<bool>("use_lo_offset") + .set(false); + this->get_tx_subtree()->create<double>("bandwidth/value") + .set(subdev_bandwidth_scalar[get_subdev_name()]*_max_freq); + this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(subdev_bandwidth_scalar[get_subdev_name()]*_max_freq, subdev_bandwidth_scalar[get_subdev_name()]*_max_freq)); + + //disable TX dboard clock by default + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, false); + + //set GPIOs to output 0x0000 to decrease noise pickup + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, 0x0000); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, 0xFFFF); + this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0x0000); +} + +basic_tx::~basic_tx(void){ + /* NOP */ +} diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp new file mode 100644 index 000000000..846597f06 --- /dev/null +++ b/host/lib/usrp/dboard/db_dbsrx.cpp @@ -0,0 +1,537 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// No RX IO Pins Used + +// RX IO Functions + +#include "max2118_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The DBSRX constants + **********************************************************************/ +static const freq_range_t dbsrx_freq_range(0.8e9, 2.4e9); + +//Multiplied by 2.0 for conversion to complex bandpass from lowpass +static const freq_range_t dbsrx_bandwidth_range(2.0*4.0e6, 2.0*33.0e6); + +static const freq_range_t dbsrx_pfd_freq_range(0.15e6, 2.01e6); + +static const std::vector<std::string> dbsrx_antennas = list_of("J3"); + +static const uhd::dict<std::string, gain_range_t> dbsrx_gain_ranges = map_list_of + ("GC1", gain_range_t(0, 56, 0.5)) + ("GC2", gain_range_t(0, 24, 1)) +; + +/*********************************************************************** + * The DBSRX dboard class + **********************************************************************/ +class dbsrx : public rx_dboard_base{ +public: + dbsrx(ctor_args_t args); + ~dbsrx(void); + +private: + double _lo_freq; + double _bandwidth; + uhd::dict<std::string, double> _gains; + max2118_write_regs_t _max2118_write_regs; + max2118_read_regs_t _max2118_read_regs; + boost::uint8_t _max2118_addr(void){ + return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x65 : 0x67; + }; + + double set_lo_freq(double target_freq); + double set_gain(double gain, const std::string &name); + double set_bandwidth(double bandwidth); + + void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ + start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x5)); + stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x5)); + + for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t) - 1){ + int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) - 1 ? sizeof(boost::uint32_t) - 1 : stop_reg - start_addr + 1; + + //create buffer for register data (+1 for start address) + byte_vector_t regs_vector(num_bytes + 1); + + //first byte is the address of first register + regs_vector[0] = start_addr; + + //get the register data + for(int i=0; i<num_bytes; i++){ + regs_vector[1+i] = _max2118_write_regs.get_reg(start_addr+i); + UHD_LOGV(often) << boost::format( + "DBSRX: send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" + ) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl; + } + + //send the data + this->get_iface()->write_i2c( + _max2118_addr(), regs_vector + ); + } + } + + void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ + static const boost::uint8_t status_addr = 0x0; + start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x1)); + stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x1)); + + for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t)){ + int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) ? sizeof(boost::uint32_t) : stop_reg - start_addr + 1; + + //create buffer for register data + byte_vector_t regs_vector(num_bytes); + + //read from i2c + regs_vector = this->get_iface()->read_i2c( + _max2118_addr(), num_bytes + ); + + for(boost::uint8_t i=0; i < num_bytes; i++){ + if (i + start_addr >= status_addr){ + _max2118_read_regs.set_reg(i + start_addr, regs_vector[i]); + } + UHD_LOGV(often) << boost::format( + "DBSRX: read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" + ) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl; + } + } + } + + /*! + * Get the lock detect status of the LO. + * \return sensor for locked + */ + sensor_value_t get_locked(void){ + read_reg(0x0, 0x0); + + //mask and return lock detect + bool locked = 5 >= _max2118_read_regs.adc and _max2118_read_regs.adc >= 2; + + UHD_LOGV(often) << boost::format( + "DBSRX: locked %d" + ) % locked << std::endl; + + return sensor_value_t("LO", locked, "locked", "unlocked"); + } +}; + +/*********************************************************************** + * Register the DBSRX dboard + **********************************************************************/ +static dboard_base::sptr make_dbsrx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new dbsrx(args)); +} + +UHD_STATIC_BLOCK(reg_dbsrx_dboard){ + //register the factory function for the rx dbid (others version) + dboard_manager::register_dboard(0x000D, &make_dbsrx, "DBSRX"); + //register the factory function for the rx dbid (USRP1 version) + dboard_manager::register_dboard(0x0002, &make_dbsrx, "DBSRX"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){ + //warn user about incorrect DBID on USRP1, requires R193 populated + if (this->get_iface()->get_special_props().soft_clock_divider and this->get_rx_id() == 0x000D) + UHD_MSG(warning) << boost::format( + "DBSRX: incorrect dbid\n" + "Expected dbid 0x0002 and R193\n" + "found dbid == %d\n" + "Please see the daughterboard app notes" + ) % this->get_rx_id().to_pp_string(); + + //warn user about incorrect DBID on non-USRP1, requires R194 populated + if (not this->get_iface()->get_special_props().soft_clock_divider and this->get_rx_id() == 0x0002) + UHD_MSG(warning) << boost::format( + "DBSRX: incorrect dbid\n" + "Expected dbid 0x000D and R194\n" + "found dbid == %d\n" + "Please see the daughterboard app notes" + ) % this->get_rx_id().to_pp_string(); + + //send initial register settings + this->send_reg(0x0, 0x5); + + //set defaults for LO, gains, and filter bandwidth + double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); + _bandwidth = 0.8*codec_rate/2.0; // default to anti-alias at different codec_rate + + //////////////////////////////////////////////////////////////////// + // Register properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name") + .set(get_rx_id().to_pp_string()); + this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&dbsrx::get_locked, this)); + BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){ + this->get_rx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&dbsrx::set_gain, this, _1, name)) + .set(dbsrx_gain_ranges[name].start()); + this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(dbsrx_gain_ranges[name]); + } + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&dbsrx::set_lo_freq, this, _1)); + this->get_rx_subtree()->create<meta_range_t>("freq/range") + .set(dbsrx_freq_range); + this->get_rx_subtree()->create<std::string>("antenna/value") + .set(dbsrx_antennas.at(0)); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(dbsrx_antennas); + this->get_rx_subtree()->create<std::string>("connection") + .set("IQ"); + this->get_rx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + this->get_rx_subtree()->create<double>("bandwidth/value") + .coerce(boost::bind(&dbsrx::set_bandwidth, this, _1)); + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(dbsrx_bandwidth_range); + + //enable only the clocks we need + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + + //set the gpio directions and atr controls (identically) + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr + if (this->get_iface()->get_special_props().soft_clock_divider){ + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x1); // GPIO0 is clock when on USRP1 + } + else{ + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs + } + + //now its safe to set inital freq and bw + this->get_rx_subtree()->access<double>("freq/value") + .set(dbsrx_freq_range.start()); + this->get_rx_subtree()->access<double>("bandwidth/value") + .set(2.0*_bandwidth); //_bandwidth in lowpass, convert to complex bandpass +} + +dbsrx::~dbsrx(void){ +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +double dbsrx::set_lo_freq(double target_freq){ + target_freq = dbsrx_freq_range.clip(target_freq); + + double actual_freq=0.0, pfd_freq=0.0, ref_clock=0.0; + int R=0, N=0, r=0, m=0; + bool update_filter_settings = false; + //choose refclock + std::vector<double> clock_rates = this->get_iface()->get_clock_rates(dboard_iface::UNIT_RX); + const double max_clock_rate = uhd::sorted(clock_rates).back(); + BOOST_FOREACH(ref_clock, uhd::reversed(uhd::sorted(clock_rates))){ + if (ref_clock > 27.0e6) continue; + if (size_t(max_clock_rate/ref_clock)%2 == 1) continue; //reject asymmetric clocks (odd divisors) + + //choose m_divider such that filter tuning constraint is met + m = 31; + while ((ref_clock/m < 1e6 or ref_clock/m > 2.5e6) and m > 0){ m--; } + + UHD_LOGV(often) << boost::format( + "DBSRX: trying ref_clock %f and m_divider %d" + ) % (ref_clock) % m << std::endl; + + if (m >= 32) continue; + + //choose R + for(r = 0; r <= 6; r += 1) { + //compute divider from setting + R = 1 << (r+1); + UHD_LOGV(often) << boost::format("DBSRX R:%d\n") % R << std::endl; + + //compute PFD compare frequency = ref_clock/R + pfd_freq = ref_clock / R; + + //constrain the PFD frequency to specified range + if ((pfd_freq < dbsrx_pfd_freq_range.start()) or (pfd_freq > dbsrx_pfd_freq_range.stop())) continue; + + //compute N + N = int(std::floor(target_freq/pfd_freq)); + + //constrain N to specified range + if ((N < 256) or (N > 32768)) continue; + + goto done_loop; + } + } + + done_loop: + + //Assert because we failed to find a suitable combination of ref_clock, R and N + UHD_ASSERT_THROW(ref_clock <= 27.0e6 and ref_clock >= 0.0); + UHD_ASSERT_THROW(ref_clock/m >= 1e6 and ref_clock/m <= 2.5e6); + UHD_ASSERT_THROW((pfd_freq >= dbsrx_pfd_freq_range.start()) and (pfd_freq <= dbsrx_pfd_freq_range.stop())); + UHD_ASSERT_THROW((N >= 256) and (N <= 32768)); + + UHD_LOGV(often) << boost::format( + "DBSRX: choose ref_clock (current: %f, new: %f) and m_divider %d" + ) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % ref_clock % m << std::endl; + + //if ref_clock or m divider changed, we need to update the filter settings + if (ref_clock != this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) or m != _max2118_write_regs.m_divider) update_filter_settings = true; + + //compute resulting output frequency + actual_freq = pfd_freq * N; + + //apply ref_clock, R, and N settings + this->get_iface()->set_clock_rate(dboard_iface::UNIT_RX, ref_clock); + ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); + _max2118_write_regs.m_divider = m; + _max2118_write_regs.r_divider = (max2118_write_regs_t::r_divider_t) r; + _max2118_write_regs.set_n_divider(N); + _max2118_write_regs.ade_vco_ade_read = max2118_write_regs_t::ADE_VCO_ADE_READ_ENABLED; + + //compute prescaler variables + int scaler = actual_freq > 1125e6 ? 2 : 4; + _max2118_write_regs.div2 = scaler == 4 ? max2118_write_regs_t::DIV2_DIV4 : max2118_write_regs_t::DIV2_DIV2; + + UHD_LOGV(often) << boost::format( + "DBSRX: scaler %d, actual_freq %f MHz, register bit: %d" + ) % scaler % (actual_freq/1e6) % int(_max2118_write_regs.div2) << std::endl; + + //compute vco frequency and select vco + double vco_freq = actual_freq * scaler; + if (vco_freq < 2433e6) + _max2118_write_regs.osc_band = 0; + else if (vco_freq < 2711e6) + _max2118_write_regs.osc_band = 1; + else if (vco_freq < 3025e6) + _max2118_write_regs.osc_band = 2; + else if (vco_freq < 3341e6) + _max2118_write_regs.osc_band = 3; + else if (vco_freq < 3727e6) + _max2118_write_regs.osc_band = 4; + else if (vco_freq < 4143e6) + _max2118_write_regs.osc_band = 5; + else if (vco_freq < 4493e6) + _max2118_write_regs.osc_band = 6; + else + _max2118_write_regs.osc_band = 7; + + //send settings over i2c + send_reg(0x0, 0x4); + + //check vtune for lock condition + read_reg(0x0, 0x0); + + UHD_LOGV(often) << boost::format( + "DBSRX: initial guess for vco %d, vtune adc %d" + ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + + //if we are out of lock for chosen vco, change vco + while ((_max2118_read_regs.adc == 0) or (_max2118_read_regs.adc == 7)){ + + //vtune is too low, try lower frequency vco + if (_max2118_read_regs.adc == 0){ + if (_max2118_write_regs.osc_band == 0){ + UHD_MSG(warning) << boost::format( + "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n" + ) % int(_max2118_write_regs.osc_band); + UHD_ASSERT_THROW(_max2118_read_regs.adc != 0); //just to cause a throw + } + if (_max2118_write_regs.osc_band <= 0) break; + _max2118_write_regs.osc_band -= 1; + } + + //vtune is too high, try higher frequency vco + if (_max2118_read_regs.adc == 7){ + if (_max2118_write_regs.osc_band == 7){ + UHD_MSG(warning) << boost::format( + "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n" + ) % int(_max2118_write_regs.osc_band); + UHD_ASSERT_THROW(_max2118_read_regs.adc != 7); //just to cause a throw + } + if (_max2118_write_regs.osc_band >= 7) break; + _max2118_write_regs.osc_band += 1; + } + + UHD_LOGV(often) << boost::format( + "DBSRX: trying vco %d, vtune adc %d" + ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + + //update vco selection and check vtune + send_reg(0x2, 0x2); + read_reg(0x0, 0x0); + + //allow for setup time before checking condition again + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + } + + UHD_LOGV(often) << boost::format( + "DBSRX: final vco %d, vtune adc %d" + ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + + //select charge pump bias current + if (_max2118_read_regs.adc <= 2) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_100UA; + else if (_max2118_read_regs.adc >= 5) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_400UA; + else _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_200UA; + + //update charge pump bias current setting + send_reg(0x2, 0x2); + + //compute actual tuned frequency + _lo_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) / std::pow(2.0,(1 + _max2118_write_regs.r_divider)) * _max2118_write_regs.get_n_divider(); + + //debug output of calculated variables + UHD_LOGV(often) + << boost::format("DBSRX tune:\n") + << boost::format(" VCO=%d, CP=%d, PFD Freq=%fMHz\n") % int(_max2118_write_regs.osc_band) % _max2118_write_regs.cp_current % (pfd_freq/1e6) + << boost::format(" R=%d, N=%f, scaler=%d, div2=%d\n") % R % N % scaler % int(_max2118_write_regs.div2) + << boost::format(" Ref Freq=%fMHz\n") % (ref_clock/1e6) + << boost::format(" Target Freq=%fMHz\n") % (target_freq/1e6) + << boost::format(" Actual Freq=%fMHz\n") % (_lo_freq/1e6) + << boost::format(" VCO Freq=%fMHz\n") % (vco_freq/1e6) + << std::endl; + + if (update_filter_settings) set_bandwidth(_bandwidth); + get_locked(); + + return _lo_freq; +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Convert a requested gain for the GC2 vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 5 bit the register value + */ +static int gain_to_gc2_vga_reg(double &gain){ + int reg = 0; + gain = dbsrx_gain_ranges["GC2"].clip(gain); + + // Half dB steps from 0-5dB, 1dB steps from 5-24dB + if (gain < 5) { + reg = boost::math::iround(31.0 - gain/0.5); + gain = double(boost::math::iround(gain) * 0.5); + } else { + reg = boost::math::iround(22.0 - (gain - 4.0)); + gain = double(boost::math::iround(gain)); + } + + UHD_LOGV(often) << boost::format( + "DBSRX GC2 Gain: %f dB, reg: %d" + ) % gain % reg << std::endl; + + return reg; +} + +/*! + * Convert a requested gain for the GC1 rf vga into the dac_volts value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ +static double gain_to_gc1_rfvga_dac(double &gain){ + //clip the input + gain = dbsrx_gain_ranges["GC1"].clip(gain); + + //voltage level constants + static const double max_volts = 1.2, min_volts = 2.7; + static const double slope = (max_volts-min_volts)/dbsrx_gain_ranges["GC1"].stop(); + + //calculate the voltage for the aux dac + double dac_volts = gain*slope + min_volts; + + UHD_LOGV(often) << boost::format( + "DBSRX GC1 Gain: %f dB, dac_volts: %f V" + ) % gain % dac_volts << std::endl; + + //the actual gain setting + gain = (dac_volts - min_volts)/slope; + + return dac_volts; +} + +double dbsrx::set_gain(double gain, const std::string &name){ + assert_has(dbsrx_gain_ranges.keys(), name, "dbsrx gain name"); + if (name == "GC2"){ + _max2118_write_regs.gc2 = gain_to_gc2_vga_reg(gain); + send_reg(0x5, 0x5); + } + else if(name == "GC1"){ + //write the new voltage to the aux dac + this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, gain_to_gc1_rfvga_dac(gain)); + } + else UHD_THROW_INVALID_CODE_PATH(); + _gains[name] = gain; + + return gain; +} + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +double dbsrx::set_bandwidth(double bandwidth){ + //convert complex bandpass to lowpass bandwidth + bandwidth = bandwidth/2.0; + + //clip the input + bandwidth = dbsrx_bandwidth_range.clip(bandwidth); + + double ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); + + //NOTE: _max2118_write_regs.m_divider set in set_lo_freq + + //compute f_dac setting + _max2118_write_regs.f_dac = uhd::clip<int>(int((((bandwidth*_max2118_write_regs.m_divider)/ref_clock) - 4)/0.145),0,127); + + //determine actual bandwidth + _bandwidth = double((ref_clock/(_max2118_write_regs.m_divider))*(4+0.145*_max2118_write_regs.f_dac)); + + UHD_LOGV(often) << boost::format( + "DBSRX Filter Bandwidth: %f MHz, m: %d, f_dac: %d\n" + ) % (_bandwidth/1e6) % int(_max2118_write_regs.m_divider) % int(_max2118_write_regs.f_dac) << std::endl; + + this->send_reg(0x3, 0x4); + + //convert lowpass back to complex bandpass bandwidth + return 2.0*_bandwidth; +} diff --git a/host/lib/usrp/dboard/db_dbsrx2.cpp b/host/lib/usrp/dboard/db_dbsrx2.cpp new file mode 100644 index 000000000..517b7b183 --- /dev/null +++ b/host/lib/usrp/dboard/db_dbsrx2.cpp @@ -0,0 +1,379 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// No RX IO Pins Used + +#include "max2112_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The DBSRX2 constants + **********************************************************************/ +static const freq_range_t dbsrx2_freq_range(0.8e9, 2.4e9); + +//Multiplied by 2.0 for conversion to complex bandpass from lowpass +static const freq_range_t dbsrx2_bandwidth_range(2.0*4.0e6, 2.0*40.0e6); + +static const int dbsrx2_ref_divider = 4; // Hitachi HMC426 divider (U7) + +static const std::vector<std::string> dbsrx2_antennas = list_of("J3"); + +static const uhd::dict<std::string, gain_range_t> dbsrx2_gain_ranges = map_list_of + ("GC1", gain_range_t(0, 73, 0.05)) + ("BBG", gain_range_t(0, 15, 1)) +; + +/*********************************************************************** + * The DBSRX2 dboard class + **********************************************************************/ +class dbsrx2 : public rx_dboard_base{ +public: + dbsrx2(ctor_args_t args); + ~dbsrx2(void); + +private: + double _lo_freq; + double _bandwidth; + uhd::dict<std::string, double> _gains; + max2112_write_regs_t _max2112_write_regs; + max2112_read_regs_t _max2112_read_regs; + boost::uint8_t _max2112_addr(){ //0x60 or 0x61 depending on which side + return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x60 : 0x61; + } + + double set_lo_freq(double target_freq); + double set_gain(double gain, const std::string &name); + double set_bandwidth(double bandwidth); + + void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ + start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0xB)); + stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0xB)); + + for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t) - 1){ + int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) - 1 ? sizeof(boost::uint32_t) - 1 : stop_reg - start_addr + 1; + + //create buffer for register data (+1 for start address) + byte_vector_t regs_vector(num_bytes + 1); + + //first byte is the address of first register + regs_vector[0] = start_addr; + + //get the register data + for(int i=0; i<num_bytes; i++){ + regs_vector[1+i] = _max2112_write_regs.get_reg(start_addr+i); + UHD_LOGV(often) << boost::format( + "DBSRX2: send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" + ) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl; + } + + //send the data + this->get_iface()->write_i2c( + _max2112_addr(), regs_vector + ); + } + } + + void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ + static const boost::uint8_t status_addr = 0xC; + start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0xD)); + stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0xD)); + + for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t)){ + int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) ? sizeof(boost::uint32_t) : stop_reg - start_addr + 1; + + //create address to start reading register data + byte_vector_t address_vector(1); + address_vector[0] = start_addr; + + //send the address + this->get_iface()->write_i2c( + _max2112_addr(), address_vector + ); + + //create buffer for register data + byte_vector_t regs_vector(num_bytes); + + //read from i2c + regs_vector = this->get_iface()->read_i2c( + _max2112_addr(), num_bytes + ); + + for(boost::uint8_t i=0; i < num_bytes; i++){ + if (i + start_addr >= status_addr){ + _max2112_read_regs.set_reg(i + start_addr, regs_vector[i]); + /* + UHD_LOGV(always) << boost::format( + "DBSRX2: set reg 0x%02x, value 0x%04x" + ) % int(i + start_addr) % int(_max2112_read_regs.get_reg(i + start_addr)) << std::endl; + */ + } + UHD_LOGV(often) << boost::format( + "DBSRX2: read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" + ) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl; + } + } + } + + /*! + * Get the lock detect status of the LO. + * \return sensor for locked + */ + sensor_value_t get_locked(void){ + read_reg(0xC, 0xD); + + //mask and return lock detect + bool locked = (_max2112_read_regs.ld & _max2112_read_regs.vasa & _max2112_read_regs.vase) != 0; + + UHD_LOGV(often) << boost::format( + "DBSRX2 locked: %d" + ) % locked << std::endl; + + return sensor_value_t("LO", locked, "locked", "unlocked"); + } +}; + +/*********************************************************************** + * Register the DBSRX2 dboard + **********************************************************************/ +// FIXME 0x67 is the default i2c address on USRP2 +// need to handle which side for USRP1 with different address +static dboard_base::sptr make_dbsrx2(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new dbsrx2(args)); +} + +UHD_STATIC_BLOCK(reg_dbsrx2_dboard){ + //register the factory function for the rx dbid + dboard_manager::register_dboard(0x0012, &make_dbsrx2, "DBSRX2"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ + //send initial register settings + send_reg(0x0, 0xB); + //for (boost::uint8_t addr=0; addr<=12; addr++) this->send_reg(addr, addr); + + //////////////////////////////////////////////////////////////////// + // Register properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name") + .set(get_rx_id().to_pp_string()); + this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&dbsrx2::get_locked, this)); + BOOST_FOREACH(const std::string &name, dbsrx2_gain_ranges.keys()){ + this->get_rx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&dbsrx2::set_gain, this, _1, name)) + .set(dbsrx2_gain_ranges[name].start()); + this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(dbsrx2_gain_ranges[name]); + } + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&dbsrx2::set_lo_freq, this, _1)) + .set(dbsrx2_freq_range.start()); + this->get_rx_subtree()->create<meta_range_t>("freq/range") + .set(dbsrx2_freq_range); + this->get_rx_subtree()->create<std::string>("antenna/value") + .set(dbsrx2_antennas.at(0)); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(dbsrx2_antennas); + this->get_rx_subtree()->create<std::string>("connection") + .set("QI"); + this->get_rx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + + double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); + + this->get_rx_subtree()->create<double>("bandwidth/value") + .coerce(boost::bind(&dbsrx2::set_bandwidth, this, _1)) + .set(2.0*(0.8*codec_rate/2.0)); //bandwidth in lowpass, convert to complex bandpass + //default to anti-alias at different codec_rate + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(dbsrx2_bandwidth_range); + + //enable only the clocks we need + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + + //set the gpio directions and atr controls (identically) + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs + + get_locked(); +} + +dbsrx2::~dbsrx2(void){ +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +double dbsrx2::set_lo_freq(double target_freq){ + //target_freq = dbsrx2_freq_range.clip(target_freq); + + //variables used in the calculation below + int scaler = target_freq > 1125e6 ? 2 : 4; + double ref_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); + int R, intdiv, fracdiv, ext_div; + double N; + + //compute tuning variables + ext_div = dbsrx2_ref_divider; // 12MHz < ref_freq/ext_divider < 30MHz + + R = 1; //Divide by 1 is the only tested value + + N = (target_freq*R*ext_div)/(ref_freq); //actual spec range is (19, 251) + intdiv = int(std::floor(N)); // if (intdiv < 19 or intdiv > 251) continue; + fracdiv = boost::math::iround((N - intdiv)*double(1 << 20)); + + //calculate the actual freq from the values above + N = double(intdiv) + double(fracdiv)/double(1 << 20); + _lo_freq = (N*ref_freq)/(R*ext_div); + + //load new counters into registers + _max2112_write_regs.set_n_divider(intdiv); + _max2112_write_regs.set_f_divider(fracdiv); + _max2112_write_regs.r_divider = R; + _max2112_write_regs.d24 = scaler == 4 ? max2112_write_regs_t::D24_DIV4 : max2112_write_regs_t::D24_DIV2; + + //debug output of calculated variables + UHD_LOGV(often) + << boost::format("DBSRX2 tune:\n") + << boost::format(" R=%d, N=%f, scaler=%d, ext_div=%d\n") % R % N % scaler % ext_div + << boost::format(" int=%d, frac=%d, d24=%d\n") % intdiv % fracdiv % int(_max2112_write_regs.d24) + << boost::format(" Ref Freq=%fMHz\n") % (ref_freq/1e6) + << boost::format(" Target Freq=%fMHz\n") % (target_freq/1e6) + << boost::format(" Actual Freq=%fMHz\n") % (_lo_freq/1e6) + << std::endl; + + //send the registers + send_reg(0x0, 0x7); + + //FIXME: probably unnecessary to call get_locked here + //get_locked(); + + return _lo_freq; +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Convert a requested gain for the BBG vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 4 bit the register value + */ +static int gain_to_bbg_vga_reg(double &gain){ + int reg = boost::math::iround(dbsrx2_gain_ranges["BBG"].clip(gain)); + + gain = double(reg); + + UHD_LOGV(often) + << boost::format("DBSRX2 BBG Gain:\n") + << boost::format(" %f dB, bbg: %d") % gain % reg + << std::endl; + + return reg; +} + +/*! + * Convert a requested gain for the GC1 rf vga into the dac_volts value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ +static double gain_to_gc1_rfvga_dac(double &gain){ + //clip the input + gain = dbsrx2_gain_ranges["GC1"].clip(gain); + + //voltage level constants + static const double max_volts = 0.5, min_volts = 2.7; + static const double slope = (max_volts-min_volts)/dbsrx2_gain_ranges["GC1"].stop(); + + //calculate the voltage for the aux dac + double dac_volts = gain*slope + min_volts; + + UHD_LOGV(often) + << boost::format("DBSRX2 GC1 Gain:\n") + << boost::format(" %f dB, dac_volts: %f V") % gain % dac_volts + << std::endl; + + //the actual gain setting + gain = (dac_volts - min_volts)/slope; + + return dac_volts; +} + +double dbsrx2::set_gain(double gain, const std::string &name){ + assert_has(dbsrx2_gain_ranges.keys(), name, "dbsrx2 gain name"); + if (name == "BBG"){ + _max2112_write_regs.bbg = gain_to_bbg_vga_reg(gain); + send_reg(0x9, 0x9); + } + else if(name == "GC1"){ + //write the new voltage to the aux dac + this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, gain_to_gc1_rfvga_dac(gain)); + } + else UHD_THROW_INVALID_CODE_PATH(); + _gains[name] = gain; + + return gain; +} + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +double dbsrx2::set_bandwidth(double bandwidth){ + //convert complex bandpass to lowpass bandwidth + bandwidth = bandwidth/2.0; + + //clip the input + bandwidth = dbsrx2_bandwidth_range.clip(bandwidth); + + _max2112_write_regs.lp = int((bandwidth/1e6 - 4)/0.29 + 12); + _bandwidth = double(4 + (_max2112_write_regs.lp - 12) * 0.29)*1e6; + + UHD_LOGV(often) + << boost::format("DBSRX2 Bandwidth:\n") + << boost::format(" %f MHz, lp: %f V") % (_bandwidth/1e6) % int(_max2112_write_regs.lp) + << std::endl; + + this->send_reg(0x8, 0x8); + + //convert lowpass back to complex bandpass bandwidth + return 2.0*_bandwidth; +} diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp new file mode 100644 index 000000000..32aa3fe04 --- /dev/null +++ b/host/lib/usrp/dboard/db_rfx.cpp @@ -0,0 +1,435 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// IO Pin functions +#define POWER_IO (1 << 7) // Low enables power supply +#define ANTSW_IO (1 << 6) // On TX DB, 0 = TX, 1 = RX, on RX DB 0 = main ant, 1 = RX2 +#define MIXER_IO (1 << 5) // Enable appropriate mixer +#define LOCKDET_MASK (1 << 2) // Input pin + +// Mixer constants +#define MIXER_ENB MIXER_IO +#define MIXER_DIS 0 + +// Antenna constants +#define ANT_TX 0 //the tx line is transmitting +#define ANT_RX ANTSW_IO //the tx line is receiving +#define ANT_TXRX 0 //the rx line is on txrx +#define ANT_RX2 ANTSW_IO //the rx line in on rx2 +#define ANT_XX 0 //dont care how the antenna is set + +#include "adf4360_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The RFX Series constants + **********************************************************************/ +static const std::vector<std::string> rfx_tx_antennas = list_of("TX/RX")("CAL"); + +static const std::vector<std::string> rfx_rx_antennas = list_of("TX/RX")("RX2")("CAL"); + +static const uhd::dict<std::string, gain_range_t> rfx_rx_gain_ranges = map_list_of + ("PGA0", gain_range_t(0, 70, 0.022)) +; + +static const uhd::dict<std::string, gain_range_t> rfx400_rx_gain_ranges = map_list_of + ("PGA0", gain_range_t(0, 45, 0.022)) +; + +/*********************************************************************** + * The RFX series of dboards + **********************************************************************/ +class rfx_xcvr : public xcvr_dboard_base{ +public: + rfx_xcvr( + ctor_args_t args, + const freq_range_t &freq_range, + bool rx_div2, bool tx_div2 + ); + ~rfx_xcvr(void); + +private: + const freq_range_t _freq_range; + const uhd::dict<std::string, gain_range_t> _rx_gain_ranges; + const uhd::dict<dboard_iface::unit_t, bool> _div2; + std::string _rx_ant; + uhd::dict<std::string, double> _rx_gains; + boost::uint16_t _power_up; + + void set_rx_ant(const std::string &ant); + void set_tx_ant(const std::string &ant); + double set_rx_gain(double gain, const std::string &name); + + /*! + * Set the LO frequency for the particular dboard unit. + * \param unit which unit rx or tx + * \param target_freq the desired frequency in Hz + * \return the actual frequency in Hz + */ + double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + + /*! + * Get the lock detect status of the LO. + * \param unit which unit rx or tx + * \return sensor for locked + */ + sensor_value_t get_locked(dboard_iface::unit_t unit){ + const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; + return sensor_value_t("LO", locked, "locked", "unlocked"); + } + + /*! + * Removed incorrect/confusing RSSI calculation + * Limited dynamic range of sensor makes this less useful + */ +}; + +/*********************************************************************** + * Register the RFX dboards (min freq, max freq, rx div2, tx div2) + **********************************************************************/ +static dboard_base::sptr make_rfx_flex400(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(400e6, 500e6), true, true)); +} + +static dboard_base::sptr make_rfx_flex900(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(750e6, 1050e6), true, true)); +} + +static dboard_base::sptr make_rfx_flex1800(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(1500e6, 2100e6), false, false)); +} + +static dboard_base::sptr make_rfx_flex1200(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(1150e6, 1450e6), true, true)); +} + +static dboard_base::sptr make_rfx_flex2200(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(2000e6, 2400e6), false, false)); +} + +static dboard_base::sptr make_rfx_flex2400(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(2300e6, 2900e6), false, false)); +} + +UHD_STATIC_BLOCK(reg_rfx_dboards){ + dboard_manager::register_dboard(0x0024, 0x0028, &make_rfx_flex400, "RFX400"); + dboard_manager::register_dboard(0x0025, 0x0029, &make_rfx_flex900, "RFX900"); + dboard_manager::register_dboard(0x0034, 0x0035, &make_rfx_flex1800, "RFX1800"); + dboard_manager::register_dboard(0x0026, 0x002a, &make_rfx_flex1200, "RFX1200"); + dboard_manager::register_dboard(0x002c, 0x002d, &make_rfx_flex2200, "RFX2200"); + dboard_manager::register_dboard(0x0027, 0x002b, &make_rfx_flex2400, "RFX2400"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +rfx_xcvr::rfx_xcvr( + ctor_args_t args, + const freq_range_t &freq_range, + bool rx_div2, bool tx_div2 +): + xcvr_dboard_base(args), + _freq_range(freq_range), + _rx_gain_ranges((get_rx_id() == 0x0024)? + rfx400_rx_gain_ranges : rfx_rx_gain_ranges + ), + _div2(map_list_of + (dboard_iface::UNIT_RX, rx_div2) + (dboard_iface::UNIT_TX, tx_div2) + ), + _power_up((get_rx_id() == 0x0024 && get_tx_id() == 0x0028) ? POWER_IO : 0) +{ + //////////////////////////////////////////////////////////////////// + // Register RX properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name").set("RFX RX"); + this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); + BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){ + this->get_rx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name)) + .set(_rx_gain_ranges[name].start()); + this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(_rx_gain_ranges[name]); + } + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set((_freq_range.start() + _freq_range.stop())/2.0); + this->get_rx_subtree()->create<meta_range_t>("freq/range").set(_freq_range); + this->get_rx_subtree()->create<std::string>("antenna/value") + .subscribe(boost::bind(&rfx_xcvr::set_rx_ant, this, _1)) + .set("RX2"); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(rfx_rx_antennas); + this->get_rx_subtree()->create<std::string>("connection").set("QI"); + this->get_rx_subtree()->create<bool>("enabled").set(true); //always enabled + this->get_rx_subtree()->create<bool>("use_lo_offset").set(false); + this->get_rx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(2*20.0e6, 2*20.0e6)); + + //////////////////////////////////////////////////////////////////// + // Register TX properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<std::string>("name").set("RFX TX"); + this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); + this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists + this->get_tx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set((_freq_range.start() + _freq_range.stop())/2.0); + this->get_tx_subtree()->create<meta_range_t>("freq/range").set(_freq_range); + this->get_tx_subtree()->create<std::string>("antenna/value") + .subscribe(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0)); + this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(rfx_tx_antennas); + this->get_tx_subtree()->create<std::string>("connection").set("IQ"); + this->get_tx_subtree()->create<bool>("enabled").set(true); //always enabled + this->get_tx_subtree()->create<bool>("use_lo_offset").set(true); + this->get_tx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(2*20.0e6, 2*20.0e6)); + + //enable the clocks that we need + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + + //set the gpio directions and atr controls (identically) + boost::uint16_t output_enables = POWER_IO | ANTSW_IO | MIXER_IO; + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, output_enables); + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, output_enables); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, output_enables); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, output_enables); + + //setup the tx atr (this does not change with antenna) + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + + //setup the rx atr (this does not change with antenna) + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); +} + +rfx_xcvr::~rfx_xcvr(void){ + /* NOP */ +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void rfx_xcvr::set_rx_ant(const std::string &ant){ + //validate input + assert_has(rfx_rx_antennas, ant, "rfx rx antenna name"); + + //set the rx atr regs that change with antenna setting + if (ant == "CAL") { + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX ); + } + else { + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | + ((ant == "TX/RX")? ANT_TXRX : ANT_RX2)); + } + + //shadow the setting + _rx_ant = ant; +} + +void rfx_xcvr::set_tx_ant(const std::string &ant){ + assert_has(rfx_tx_antennas, ant, "rfx tx antenna name"); + + //set the tx atr regs that change with antenna setting + if (ant == "CAL") { + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB); + } + else { + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + } +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +static double rx_pga0_gain_to_dac_volts(double &gain, double range){ + //voltage level constants (negative slope) + static const double max_volts = .2, min_volts = 1.2; + static const double slope = (max_volts-min_volts)/(range); + + //calculate the voltage for the aux dac + double dac_volts = uhd::clip<double>(gain*slope + min_volts, max_volts, min_volts); + + //the actual gain setting + gain = (dac_volts - min_volts)/slope; + + return dac_volts; +} + +double rfx_xcvr::set_rx_gain(double gain, const std::string &name){ + assert_has(_rx_gain_ranges.keys(), name, "rfx rx gain name"); + if(name == "PGA0"){ + double dac_volts = rx_pga0_gain_to_dac_volts(gain, + (_rx_gain_ranges["PGA0"].stop() - _rx_gain_ranges["PGA0"].start())); + + //write the new voltage to the aux dac + this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, dac_volts); + + return gain; + } + else UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +double rfx_xcvr::set_lo_freq( + dboard_iface::unit_t unit, + double target_freq +){ + UHD_LOGV(often) << boost::format( + "RFX tune: target frequency %f Mhz" + ) % (target_freq/1e6) << std::endl; + + //clip the input + target_freq = _freq_range.clip(target_freq); + if (_div2[unit]) target_freq *= 2; + + //rfx400 rx is a special case with div2 in mixer, so adf4360 must output fundamental + bool is_rx_rfx400 = ((get_rx_id() == 0x0024) && unit != dboard_iface::UNIT_TX); + + //map prescalers to the register enums + static const uhd::dict<int, adf4360_regs_t::prescaler_value_t> prescaler_to_enum = map_list_of + (8, adf4360_regs_t::PRESCALER_VALUE_8_9) + (16, adf4360_regs_t::PRESCALER_VALUE_16_17) + (32, adf4360_regs_t::PRESCALER_VALUE_32_33) + ; + + //map band select clock dividers to enums + static const uhd::dict<int, adf4360_regs_t::band_select_clock_div_t> bandsel_to_enum = map_list_of + (1, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_1) + (2, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_2) + (4, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_4) + (8, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_8) + ; + + double actual_freq=0, ref_freq = this->get_iface()->get_clock_rate(unit); + int R=0, BS=0, P=0, B=0, A=0; + + /* + * The goal here to to loop though possible R dividers, + * band select clock dividers, and prescaler values. + * Calculate the A and B counters for each set of values. + * The loop exists when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * fvco = [P*B + A] * fref/R + * fvco*R/fref = P*B + A = N + */ + for(R = 2; R <= 32; R+=2){ + BOOST_FOREACH(BS, bandsel_to_enum.keys()){ + if (ref_freq/R/BS > 1e6) continue; //constraint on band select clock + BOOST_FOREACH(P, prescaler_to_enum.keys()){ + //calculate B and A from N + double N = target_freq*R/ref_freq; + B = int(std::floor(N/P)); + A = boost::math::iround(N - P*B); + if (B < A or B > 8191 or B < 3 or A > 31) continue; //constraints on A, B + //calculate the actual frequency + actual_freq = double(P*B + A)*ref_freq/R; + if (actual_freq/P > 300e6) continue; //constraint on prescaler output + //constraints met: exit loop + goto done_loop; + } + } + } done_loop: + + UHD_LOGV(often) << boost::format( + "RFX tune: R=%d, BS=%d, P=%d, B=%d, A=%d, DIV2=%d" + ) % R % BS % P % B % A % int(_div2[unit] && (!is_rx_rfx400)) << std::endl; + + //load the register values + adf4360_regs_t regs; + regs.core_power_level = adf4360_regs_t::CORE_POWER_LEVEL_10MA; + regs.counter_operation = adf4360_regs_t::COUNTER_OPERATION_NORMAL; + regs.muxout_control = adf4360_regs_t::MUXOUT_CONTROL_DLD; + regs.phase_detector_polarity = adf4360_regs_t::PHASE_DETECTOR_POLARITY_POS; + regs.charge_pump_output = adf4360_regs_t::CHARGE_PUMP_OUTPUT_NORMAL; + regs.cp_gain_0 = adf4360_regs_t::CP_GAIN_0_SET1; + regs.mute_till_ld = adf4360_regs_t::MUTE_TILL_LD_ENB; + regs.output_power_level = adf4360_regs_t::OUTPUT_POWER_LEVEL_3_5MA; + regs.current_setting1 = adf4360_regs_t::CURRENT_SETTING1_0_31MA; + regs.current_setting2 = adf4360_regs_t::CURRENT_SETTING2_0_31MA; + regs.power_down = adf4360_regs_t::POWER_DOWN_NORMAL_OP; + regs.prescaler_value = prescaler_to_enum[P]; + regs.a_counter = A; + regs.b_counter = B; + regs.cp_gain_1 = adf4360_regs_t::CP_GAIN_1_SET1; + regs.divide_by_2_output = (_div2[unit] && (!is_rx_rfx400)) ? // Special case RFX400 RX Mixer divides by two + adf4360_regs_t::DIVIDE_BY_2_OUTPUT_DIV2 : + adf4360_regs_t::DIVIDE_BY_2_OUTPUT_FUND ; + regs.divide_by_2_prescaler = adf4360_regs_t::DIVIDE_BY_2_PRESCALER_FUND; + regs.r_counter = R; + regs.ablpw = adf4360_regs_t::ABLPW_3_0NS; + regs.lock_detect_precision = adf4360_regs_t::LOCK_DETECT_PRECISION_5CYCLES; + regs.test_mode_bit = 0; + regs.band_select_clock_div = bandsel_to_enum[BS]; + + //write the registers + std::vector<adf4360_regs_t::addr_t> addrs = list_of //correct power-up sequence to write registers (R, C, N) + (adf4360_regs_t::ADDR_RCOUNTER) + (adf4360_regs_t::ADDR_CONTROL) + (adf4360_regs_t::ADDR_NCOUNTER) + ; + BOOST_FOREACH(adf4360_regs_t::addr_t addr, addrs){ + this->get_iface()->write_spi( + unit, spi_config_t::EDGE_RISE, + regs.get_reg(addr), 24 + ); + } + + //return the actual frequency + if (_div2[unit]) actual_freq /= 2; + UHD_LOGV(often) << boost::format( + "RFX tune: actual frequency %f Mhz" + ) % (actual_freq/1e6) << std::endl; + return actual_freq; +} diff --git a/host/lib/usrp/dboard/db_sbx_common.cpp b/host/lib/usrp/dboard/db_sbx_common.cpp new file mode 100644 index 000000000..d1cd5b373 --- /dev/null +++ b/host/lib/usrp/dboard/db_sbx_common.cpp @@ -0,0 +1,338 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "db_sbx_common.hpp" +#include "adf4350_regs.hpp" + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * Register the SBX dboard (min freq, max freq, rx div2, tx div2) + **********************************************************************/ +static dboard_base::sptr make_sbx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new sbx_xcvr(args)); +} + +UHD_STATIC_BLOCK(reg_sbx_dboards){ + dboard_manager::register_dboard(0x0054, 0x0055, &make_sbx, "SBX"); + dboard_manager::register_dboard(0x0065, 0x0064, &make_sbx, "SBX v4"); +} + + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +static int rx_pga0_gain_to_iobits(double &gain){ + //clip the input + gain = sbx_rx_gain_ranges["PGA0"].clip(gain); + + //convert to attenuation and update iobits for atr + double attn = sbx_rx_gain_ranges["PGA0"].stop() - gain; + + //calculate the RX attenuation + int attn_code = int(floor(attn*2)); + int iobits = ((~attn_code) << RX_ATTN_SHIFT) & RX_ATTN_MASK; + + UHD_LOGV(often) << boost::format( + "SBX TX 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 + gain = sbx_rx_gain_ranges["PGA0"].stop() - double(attn_code)/2; + + return iobits; +} + +static int tx_pga0_gain_to_iobits(double &gain){ + //clip the input + gain = sbx_tx_gain_ranges["PGA0"].clip(gain); + + //convert to attenuation and update iobits for atr + double attn = sbx_tx_gain_ranges["PGA0"].stop() - gain; + + //calculate the TX attenuation + int attn_code = int(floor(attn*2)); + int iobits = ((~attn_code) << TX_ATTN_SHIFT) & TX_ATTN_MASK; + + UHD_LOGV(often) << boost::format( + "SBX 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 = sbx_tx_gain_ranges["PGA0"].stop() - double(attn_code)/2; + + return iobits; +} + +double sbx_xcvr::set_tx_gain(double gain, const std::string &name){ + assert_has(sbx_tx_gain_ranges.keys(), name, "sbx tx gain name"); + if(name == "PGA0"){ + tx_pga0_gain_to_iobits(gain); + _tx_gains[name] = gain; + + //write the new gain to atr regs + update_atr(); + } + else UHD_THROW_INVALID_CODE_PATH(); + return _tx_gains[name]; +} + +double sbx_xcvr::set_rx_gain(double gain, const std::string &name){ + assert_has(sbx_rx_gain_ranges.keys(), name, "sbx rx gain name"); + if(name == "PGA0"){ + rx_pga0_gain_to_iobits(gain); + _rx_gains[name] = gain; + + //write the new gain to atr regs + update_atr(); + } + else UHD_THROW_INVALID_CODE_PATH(); + return _rx_gains[name]; +} + + +/*********************************************************************** + * Structors + **********************************************************************/ +sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ + switch(get_rx_id().to_uint16()) { + case 0x054: + db_actual = sbx_versionx_sptr(new sbx_version3(this)); + break; + case 0x065: + db_actual = sbx_versionx_sptr(new sbx_version4(this)); + break; + default: + /* We didn't recognize the version of the board... */ + UHD_THROW_INVALID_CODE_PATH(); + } + + //////////////////////////////////////////////////////////////////// + // Register RX properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name").set("SBX RX"); + this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); + BOOST_FOREACH(const std::string &name, sbx_rx_gain_ranges.keys()){ + this->get_rx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name)) + .set(sbx_rx_gain_ranges[name].start()); + this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(sbx_rx_gain_ranges[name]); + } + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set((sbx_freq_range.start() + sbx_freq_range.stop())/2.0); + this->get_rx_subtree()->create<meta_range_t>("freq/range").set(sbx_freq_range); + this->get_rx_subtree()->create<std::string>("antenna/value") + .subscribe(boost::bind(&sbx_xcvr::set_rx_ant, this, _1)) + .set("RX2"); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(sbx_rx_antennas); + this->get_rx_subtree()->create<std::string>("connection").set("IQ"); + this->get_rx_subtree()->create<bool>("enabled").set(true); //always enabled + this->get_rx_subtree()->create<bool>("use_lo_offset").set(false); + this->get_rx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(2*20.0e6, 2*20.0e6)); + + //////////////////////////////////////////////////////////////////// + // Register TX properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<std::string>("name").set("SBX TX"); + this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); + BOOST_FOREACH(const std::string &name, sbx_tx_gain_ranges.keys()){ + this->get_tx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name)) + .set(sbx_tx_gain_ranges[name].start()); + this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(sbx_tx_gain_ranges[name]); + } + this->get_tx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set((sbx_freq_range.start() + sbx_freq_range.stop())/2.0); + this->get_tx_subtree()->create<meta_range_t>("freq/range").set(sbx_freq_range); + this->get_tx_subtree()->create<std::string>("antenna/value") + .subscribe(boost::bind(&sbx_xcvr::set_tx_ant, this, _1)) + .set(sbx_tx_antennas.at(0)); + this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(sbx_tx_antennas); + this->get_tx_subtree()->create<std::string>("connection").set("QI"); + this->get_tx_subtree()->create<bool>("enabled").set(true); //always enabled + this->get_tx_subtree()->create<bool>("use_lo_offset").set(false); + this->get_tx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(2*20.0e6, 2*20.0e6)); + + //enable the clocks that we need + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + + //set the gpio directions and atr controls (identically) + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); + + //flash LEDs + flash_leds(); + + UHD_LOGV(often) << boost::format( + "SBX GPIO Direction: RX: 0x%08x, TX: 0x%08x" + ) % RXIO_MASK % TXIO_MASK << std::endl; +} + +sbx_xcvr::~sbx_xcvr(void){ + /* NOP */ +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void sbx_xcvr::update_atr(void){ + //calculate atr pins + int rx_pga0_iobits = rx_pga0_gain_to_iobits(_rx_gains["PGA0"]); + int tx_pga0_iobits = tx_pga0_gain_to_iobits(_tx_gains["PGA0"]); + int rx_lo_lpf_en = (_rx_lo_freq == sbx_enable_rx_lo_filter.clip(_rx_lo_freq)) ? LO_LPF_EN : 0; + int tx_lo_lpf_en = (_tx_lo_freq == sbx_enable_tx_lo_filter.clip(_tx_lo_freq)) ? LO_LPF_EN : 0; + int rx_ld_led = get_locked(dboard_iface::UNIT_RX).to_bool() ? 0 : RX_LED_LD; + int tx_ld_led = get_locked(dboard_iface::UNIT_TX).to_bool() ? 0 : TX_LED_LD; + int rx_ant_led = _rx_ant == "TX/RX" ? RX_LED_RX1RX2 : 0; + int tx_ant_led = _tx_ant == "TX/RX" ? 0 : TX_LED_TXRX; + + //setup the tx atr (this does not change with antenna) + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ + dboard_iface::ATR_REG_IDLE, 0 | tx_lo_lpf_en \ + | tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_XX | TX_MIXER_DIS); + + //setup the rx atr (this does not change with antenna) + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ + dboard_iface::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \ + | rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_XX | RX_MIXER_DIS); + + //set the RX atr regs that change with antenna setting + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ + dboard_iface::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ + | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \ + | ((_rx_ant != "RX2")? ANT_TXRX : ANT_RX2)); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ + dboard_iface::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ + | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_DIS \ + | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2)); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ + dboard_iface::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \ + | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \ + | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2)); + + //set the TX atr regs that change with antenna setting + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ + dboard_iface::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \ + | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_DIS \ + | ((_rx_ant != "RX2")? ANT_RX : ANT_TX)); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ + dboard_iface::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \ + | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \ + | ((_tx_ant == "CAL")? ANT_RX : ANT_TX)); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ + dboard_iface::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \ + | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \ + | ((_tx_ant == "CAL")? ANT_RX : ANT_TX)); +} + +void sbx_xcvr::set_rx_ant(const std::string &ant){ + //validate input + assert_has(sbx_rx_antennas, ant, "sbx rx antenna name"); + + //shadow the setting + _rx_ant = ant; + + //write the new antenna setting to atr regs + update_atr(); +} + +void sbx_xcvr::set_tx_ant(const std::string &ant){ + assert_has(sbx_tx_antennas, ant, "sbx tx antenna name"); + + //shadow the setting + _tx_ant = ant; + + //write the new antenna setting to atr regs + update_atr(); +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +double sbx_xcvr::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { + const double actual = db_actual->set_lo_freq(unit, target_freq); + if (unit == dboard_iface::UNIT_RX) _rx_lo_freq = actual; + if (unit == dboard_iface::UNIT_TX) _tx_lo_freq = actual; + update_atr(); + return actual; +} + + +sensor_value_t sbx_xcvr::get_locked(dboard_iface::unit_t unit) { + const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; + return sensor_value_t("LO", locked, "locked", "unlocked"); +} + + +void sbx_xcvr::flash_leds(void) { + //Remove LED gpios from ATR control temporarily and set to outputs + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|RX_LED_IO)); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, \ + TX_LED_TXRX|TX_LED_LD, TX_LED_IO); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, \ + RX_LED_RX1RX2|RX_LED_LD, RX_LED_IO); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + //Put LED gpios back in ATR control and update atr + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); +} + diff --git a/host/lib/usrp/dboard/db_sbx_common.hpp b/host/lib/usrp/dboard/db_sbx_common.hpp new file mode 100644 index 000000000..6380ddb47 --- /dev/null +++ b/host/lib/usrp/dboard/db_sbx_common.hpp @@ -0,0 +1,225 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + + + +// Common IO Pins +#define LO_LPF_EN (1 << 15) +#define SYNTH_CE (1 << 3) +#define SYNTH_PDBRF (1 << 2) +#define SYNTH_MUXOUT (1 << 1) // INPUT!!! +#define LOCKDET_MASK (1 << 0) // INPUT!!! + +// TX IO Pins +#define TRSW (1 << 14) // 0 = TX, 1 = RX +#define TX_LED_TXRX (1 << 7) // LED for TX Antenna Selection TX/RX +#define TX_LED_LD (1 << 6) // LED for TX Lock Detect +#define DIS_POWER_TX (1 << 5) // on UNIT_TX, 0 powers up TX +#define TX_ENABLE (1 << 4) // on UNIT_TX, 0 disables TX Mixer + +// RX IO Pins +#define LNASW (1 << 14) // 0 = TX/RX, 1 = RX2 +#define RX_LED_RX1RX2 (1 << 7) // LED for RX Antenna Selection RX1/RX2 +#define RX_LED_LD (1 << 6) // LED for RX Lock Detect +#define DIS_POWER_RX (1 << 5) // on UNIT_RX, 0 powers up RX +#define RX_DISABLE (1 << 4) // on UNIT_RX, 1 disables RX Mixer and Baseband + +// RX Attenuator Pins +#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 +#define TX_ATTN_SHIFT 8 // lsb of TX Attenuator Control +#define TX_ATTN_MASK (63 << TX_ATTN_SHIFT) // valid bits of TX Attenuator Control + +// Mixer functions +#define TX_MIXER_ENB (SYNTH_PDBRF|TX_ENABLE) +#define TX_MIXER_DIS 0 + +#define RX_MIXER_ENB (SYNTH_PDBRF) +#define RX_MIXER_DIS RX_DISABLE + +// Pin functions +#define TX_LED_IO (TX_LED_TXRX|TX_LED_LD) // LED gpio lines, pull down for LED +#define TXIO_MASK (LO_LPF_EN|TRSW|SYNTH_CE|SYNTH_PDBRF|TX_ATTN_MASK|DIS_POWER_TX|TX_ENABLE) + +#define RX_LED_IO (RX_LED_RX1RX2|RX_LED_LD) // LED gpio lines, pull down for LED +#define RXIO_MASK (LO_LPF_EN|LNASW|SYNTH_CE|SYNTH_PDBRF|RX_ATTN_MASK|DIS_POWER_RX|RX_DISABLE) + +// Power functions +#define TX_POWER_UP (SYNTH_CE) +#define TX_POWER_DOWN (DIS_POWER_TX) + +#define RX_POWER_UP (SYNTH_CE) +#define RX_POWER_DOWN (DIS_POWER_RX) + +// Antenna constants +#define ANT_TX TRSW //the tx line is transmitting +#define ANT_RX 0 //the tx line is receiving +#define ANT_TXRX 0 //the rx line is on txrx +#define ANT_RX2 LNASW //the rx line in on rx2 +#define ANT_XX LNASW //dont care how the antenna is set + + +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/thread.hpp> + + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * The SBX dboard constants + **********************************************************************/ +static const freq_range_t sbx_freq_range(400e6, 4.4e9); + +static const freq_range_t sbx_tx_lo_2dbm = list_of + (range_t(0.35e9, 0.37e9)) +; + +static const freq_range_t sbx_enable_tx_lo_filter = list_of + (range_t(0.4e9, 1.5e9)) +; + +static const freq_range_t sbx_enable_rx_lo_filter = list_of + (range_t(0.4e9, 1.5e9)) +; + +static const std::vector<std::string> sbx_tx_antennas = list_of("TX/RX")("CAL"); + +static const std::vector<std::string> sbx_rx_antennas = list_of("TX/RX")("RX2")("CAL"); + +static const uhd::dict<std::string, gain_range_t> sbx_tx_gain_ranges = map_list_of + ("PGA0", gain_range_t(0, 31.5, double(0.5))) +; + +static const uhd::dict<std::string, gain_range_t> sbx_rx_gain_ranges = map_list_of + ("PGA0", gain_range_t(0, 31.5, double(0.5))) +; + +/*********************************************************************** + * The SBX dboard + **********************************************************************/ +class sbx_xcvr : public xcvr_dboard_base{ +public: + sbx_xcvr(ctor_args_t args); + ~sbx_xcvr(void); + +protected: + + uhd::dict<std::string, double> _tx_gains, _rx_gains; + double _rx_lo_freq, _tx_lo_freq; + std::string _tx_ant, _rx_ant; + + void set_rx_ant(const std::string &ant); + void set_tx_ant(const std::string &ant); + double set_rx_gain(double gain, const std::string &name); + double set_tx_gain(double gain, const std::string &name); + + void update_atr(void); + + /*! + * Set the LO frequency for the particular dboard unit. + * \param unit which unit rx or tx + * \param target_freq the desired frequency in Hz + * \return the actual frequency in Hz + */ + virtual double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + + /*! + * Get the lock detect status of the LO. + * \param unit which unit rx or tx + * \return true for locked + */ + sensor_value_t get_locked(dboard_iface::unit_t unit); + + /*! + * Flash the LEDs + */ + void flash_leds(void); + + /*! + * Version-agnostic ABC that wraps version-specific implementations of the + * WBX base daughterboard. + * + * This class is an abstract base class, and thus is impossible to + * instantiate. + */ + class sbx_versionx { + public: + sbx_versionx() {} + ~sbx_versionx(void) {} + + virtual double set_lo_freq(dboard_iface::unit_t unit, double target_freq) = 0; + }; + + /*! + * Version 3 of the SBX Daughterboard + */ + class sbx_version3 : public sbx_versionx { + public: + sbx_version3(sbx_xcvr *_self_sbx_xcvr); + ~sbx_version3(void); + + double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + + /*! This is the registered instance of the wrapper class, sbx_base. */ + sbx_xcvr *self_base; + }; + + /*! + * Version 4 of the SBX Daughterboard + * + * The only difference in the fourth revision is the ADF4351 vs the ADF4350. + */ + class sbx_version4 : public sbx_versionx { + public: + sbx_version4(sbx_xcvr *_self_sbx_xcvr); + ~sbx_version4(void); + + double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + + /*! This is the registered instance of the wrapper class, sbx_base. */ + sbx_xcvr *self_base; + }; + + /*! + * Handle to the version-specific implementation of the SBX. + * + * Since many of this class's functions are dependent on the version of the + * SBX board, this class will instantiate an object of the appropriate + * sbx_version* subclass, and invoke any relevant functions through that + * object. This pointer is set to the proper object at construction time. + */ + typedef boost::shared_ptr<sbx_versionx> sbx_versionx_sptr; + sbx_versionx_sptr db_actual; +}; + diff --git a/host/lib/usrp/dboard/db_sbx_version3.cpp b/host/lib/usrp/dboard/db_sbx_version3.cpp new file mode 100644 index 000000000..6e20d5882 --- /dev/null +++ b/host/lib/usrp/dboard/db_sbx_version3.cpp @@ -0,0 +1,186 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + + +#include "adf4350_regs.hpp" +#include "db_sbx_common.hpp" + + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * Structors + **********************************************************************/ +sbx_xcvr::sbx_version3::sbx_version3(sbx_xcvr *_self_sbx_xcvr) { + //register the handle to our base SBX class + self_base = _self_sbx_xcvr; +} + +sbx_xcvr::sbx_version3::~sbx_version3(void){ + /* NOP */ +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { + UHD_LOGV(often) << boost::format( + "SBX tune: target frequency %f Mhz" + ) % (target_freq/1e6) << std::endl; + + //clip the input + target_freq = sbx_freq_range.clip(target_freq); + + //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) + static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of + (0,23) //adf4350_regs_t::PRESCALER_4_5 + (1,75) //adf4350_regs_t::PRESCALER_8_9 + ; + + //map rf divider select output dividers to enums + static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of + (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) + (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) + (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) + (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) + (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) + ; + + double actual_freq, pfd_freq; + double ref_freq = self_base->get_iface()->get_clock_rate(unit); + int R=0, BS=0, N=0, FRAC=0, MOD=0; + int RFdiv = 1; + adf4350_regs_t::reference_divide_by_2_t T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + adf4350_regs_t::reference_doubler_t D = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; + + //Reference doubler for 50% duty cycle + // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 + if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; + + //increase RF divider until acceptable VCO frequency + //start with target_freq*2 because mixer has divide by 2 + double vco_freq = target_freq; + while (vco_freq < 2.2e9) { + vco_freq *= 2; + RFdiv *= 2; + } + + //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exists when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_rf = f_vco/RFdiv) + * f_actual = f_rf/2 + */ + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = ref_freq*(1+D)/(R*(1+T)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > 25e6) continue; + + //ignore fractional part of tuning + N = int(std::floor(vco_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < prescaler_to_min_int_div[prescaler]) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below 100KHz + //constraint on band select clock + if (pfd_freq/BS > 100e3) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = int((vco_freq/pfd_freq - N)*MOD); + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0){ + T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; + R /= 2; + } + + //actual frequency calculation + actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv); + + UHD_LOGV(often) + << boost::format("SBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + << boost::format("SBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%s" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv % self_base->get_locked(unit).to_pp_string() << std::endl + << boost::format("SBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" + ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + + //load the register values + adf4350_regs_t regs; + + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) + regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; + else + regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + + regs.frac_12_bit = FRAC; + regs.int_16_bit = N; + regs.mod_12_bit = MOD; + regs.prescaler = prescaler; + regs.r_counter_10_bit = R; + regs.reference_divide_by_2 = T; + regs.reference_doubler = D; + regs.band_select_clock_div = BS; + UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); + regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + int addr; + + for(addr=5; addr>=0; addr--){ + UHD_LOGV(often) << boost::format( + "SBX SPI Reg (0x%02x): 0x%08x" + ) % addr % regs.get_reg(addr) << std::endl; + self_base->get_iface()->write_spi( + unit, spi_config_t::EDGE_RISE, + regs.get_reg(addr), 32 + ); + } + + //return the actual frequency + UHD_LOGV(often) << boost::format( + "SBX tune: actual frequency %f Mhz" + ) % (actual_freq/1e6) << std::endl; + return actual_freq; +} + diff --git a/host/lib/usrp/dboard/db_sbx_version4.cpp b/host/lib/usrp/dboard/db_sbx_version4.cpp new file mode 100644 index 000000000..c8128d5f4 --- /dev/null +++ b/host/lib/usrp/dboard/db_sbx_version4.cpp @@ -0,0 +1,189 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + + +#include "adf4351_regs.hpp" +#include "db_sbx_common.hpp" + + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * Structors + **********************************************************************/ +sbx_xcvr::sbx_version4::sbx_version4(sbx_xcvr *_self_sbx_xcvr) { + //register the handle to our base SBX class + self_base = _self_sbx_xcvr; +} + + +sbx_xcvr::sbx_version4::~sbx_version4(void){ + /* NOP */ +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { + UHD_LOGV(often) << boost::format( + "SBX tune: target frequency %f Mhz" + ) % (target_freq/1e6) << std::endl; + + //clip the input + target_freq = sbx_freq_range.clip(target_freq); + + //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) + static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of + (0,23) //adf4351_regs_t::PRESCALER_4_5 + (1,75) //adf4351_regs_t::PRESCALER_8_9 + ; + + //map rf divider select output dividers to enums + static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of + (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) + (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) + (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) + (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) + (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) + (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) + (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) + ; + + double actual_freq, pfd_freq; + double ref_freq = self_base->get_iface()->get_clock_rate(unit); + int R=0, BS=0, N=0, FRAC=0, MOD=0; + int RFdiv = 1; + adf4351_regs_t::reference_divide_by_2_t T = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + adf4351_regs_t::reference_doubler_t D = adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; + + //Reference doubler for 50% duty cycle + // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 + if(ref_freq <= 12.5e6) D = adf4351_regs_t::REFERENCE_DOUBLER_ENABLED; + + //increase RF divider until acceptable VCO frequency + //start with target_freq*2 because mixer has divide by 2 + double vco_freq = target_freq; + while (vco_freq < 2.2e9) { + vco_freq *= 2; + RFdiv *= 2; + } + + //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + adf4351_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exists when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_rf = f_vco/RFdiv) + * f_actual = f_rf/2 + */ + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = ref_freq*(1+D)/(R*(1+T)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > 25e6) continue; + + //ignore fractional part of tuning + N = int(std::floor(vco_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < prescaler_to_min_int_div[prescaler]) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below 100KHz + //constraint on band select clock + if (pfd_freq/BS > 100e3) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = int((vco_freq/pfd_freq - N)*MOD); + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0){ + T = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; + R /= 2; + } + + //actual frequency calculation + actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv); + + UHD_LOGV(often) + << boost::format("SBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + << boost::format("SBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%s" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv % self_base->get_locked(unit).to_pp_string() << std::endl + << boost::format("SBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" + ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + + //load the register values + adf4351_regs_t regs; + + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) + regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM; + else + regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; + + regs.frac_12_bit = FRAC; + regs.int_16_bit = N; + regs.mod_12_bit = MOD; + regs.prescaler = prescaler; + regs.r_counter_10_bit = R; + regs.reference_divide_by_2 = T; + regs.reference_doubler = D; + regs.band_select_clock_div = BS; + UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); + regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + int addr; + + for(addr=5; addr>=0; addr--){ + UHD_LOGV(often) << boost::format( + "SBX SPI Reg (0x%02x): 0x%08x" + ) % addr % regs.get_reg(addr) << std::endl; + self_base->get_iface()->write_spi( + unit, spi_config_t::EDGE_RISE, + regs.get_reg(addr), 32 + ); + } + + //return the actual frequency + UHD_LOGV(often) << boost::format( + "SBX tune: actual frequency %f Mhz" + ) % (actual_freq/1e6) << std::endl; + return actual_freq; +} + diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp new file mode 100644 index 000000000..fd86d5b83 --- /dev/null +++ b/host/lib/usrp/dboard/db_tvrx.cpp @@ -0,0 +1,411 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// No RX IO Pins Used + +// RX IO Functions + +//ADC/DAC functions: +//DAC 1: RF AGC +//DAC 2: IF AGC + +//min freq: 50e6 +//max freq: 860e6 +//gain range: [0:1dB:115dB] + +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/array.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> +#include <cmath> +#include <cfloat> +#include <limits> +#include <tuner_4937di5_regs.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The tvrx constants + **********************************************************************/ +static const freq_range_t tvrx_freq_range(50e6, 860e6); + +static const std::vector<std::string> tvrx_antennas = list_of("RX"); + +static const uhd::dict<std::string, freq_range_t> tvrx_freq_ranges = map_list_of + ("VHFLO", freq_range_t(50e6, 158e6)) + ("VHFHI", freq_range_t(158e6, 454e6)) + ("UHF" , freq_range_t(454e6, 860e6)) +; + +static const boost::array<double, 17> vhflo_gains_db = + {{-6.00000, -6.00000, -6.00000, -4.00000, 0.00000, + 5.00000, 10.00000, 17.40000, 26.30000, 36.00000, + 43.00000, 48.00000, 49.50000, 50.10000, 50.30000, + 50.30000, 50.30000}}; + +static const boost::array<double, 17> vhfhi_gains_db = + {{-13.3000, -13.3000, -13.3000, -1.0000, 7.7000, + 11.0000, 14.7000, 19.3000, 26.1000, 36.0000, + 42.7000, 46.0000, 47.0000, 47.8000, 48.2000, + 48.2000, 48.2000}}; + +static const boost::array<double, 17> uhf_gains_db = + {{-8.0000, -8.0000, -7.0000, 4.0000, 10.2000, + 14.5000, 17.5000, 20.0000, 24.5000, 30.8000, + 37.0000, 39.8000, 40.7000, 41.6000, 42.6000, + 43.2000, 43.8000}}; + +static const boost::array<double, 17> tvrx_if_gains_db = + {{-1.50000, -1.50000, -1.50000, -1.00000, 0.20000, + 2.10000, 4.30000, 6.40000, 9.00000, 12.00000, + 14.80000, 18.20000, 26.10000, 32.50000, 32.50000, + 32.50000, 32.50000}}; + +//gain linearization data +//this is from the datasheet and is dB vs. volts (below) +//i tried to curve fit this, but it's really just so nonlinear that you'd +//need dang near as many coefficients as to just map it like this and interp. +//these numbers are culled from the 4937DI5 datasheet and are probably totally inaccurate +//but if it's better than the old linear fit i'm happy +static const uhd::dict<std::string, boost::array<double, 17> > tvrx_rf_gains_db = map_list_of + ("VHFLO", vhflo_gains_db) + ("VHFHI", vhfhi_gains_db) + ("UHF" , uhf_gains_db) +; + +//sample voltages for the above points +static const boost::array<double, 17> tvrx_gains_volts = + {{0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6, 2.8, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0}}; + +static uhd::dict<std::string, gain_range_t> get_tvrx_gain_ranges(void) { + double rfmax = 0.0, rfmin = FLT_MAX; + BOOST_FOREACH(const std::string range, tvrx_rf_gains_db.keys()) { + double my_max = tvrx_rf_gains_db[range].back(); //we're assuming it's monotonic + double my_min = tvrx_rf_gains_db[range].front(); //if it's not this is wrong wrong wrong + if(my_max > rfmax) rfmax = my_max; + if(my_min < rfmin) rfmin = my_min; + } + + double ifmin = tvrx_if_gains_db.front(); + double ifmax = tvrx_if_gains_db.back(); + + return map_list_of + ("RF", gain_range_t(rfmin, rfmax, (rfmax-rfmin)/4096.0)) + ("IF", gain_range_t(ifmin, ifmax, (ifmax-ifmin)/4096.0)) + ; +} + +static const double opamp_gain = 1.22; //onboard DAC opamp gain +static const double tvrx_if_freq = 43.75e6; //IF freq of TVRX module +static const boost::uint16_t reference_divider = 640; //clock reference divider to use +static const double reference_freq = 4.0e6; + +/*********************************************************************** + * The tvrx dboard class + **********************************************************************/ +class tvrx : public rx_dboard_base{ +public: + tvrx(ctor_args_t args); + ~tvrx(void); + +private: + uhd::dict<std::string, double> _gains; + double _lo_freq; + tuner_4937di5_regs_t _tuner_4937di5_regs; + boost::uint8_t _tuner_4937di5_addr(void){ + return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x61 : 0x60; //ok really? we could rename that call + }; + + double set_gain(double gain, const std::string &name); + double set_freq(double freq); + + void update_regs(void){ + byte_vector_t regs_vector(4); + + //get the register data + for(int i=0; i<4; i++){ + regs_vector[i] = _tuner_4937di5_regs.get_reg(i); + UHD_LOGV(often) << boost::format( + "tvrx: send reg 0x%02x, value 0x%04x" + ) % int(i) % int(regs_vector[i]) << std::endl; + } + + //send the data + this->get_iface()->write_i2c( + _tuner_4937di5_addr(), regs_vector + ); + } + +}; + +/*********************************************************************** + * Register the tvrx dboard + **********************************************************************/ +static dboard_base::sptr make_tvrx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new tvrx(args)); +} + +UHD_STATIC_BLOCK(reg_tvrx_dboard){ + //register the factory function for the rx dbid + dboard_manager::register_dboard(0x0040, &make_tvrx, "TVRX"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){ + //////////////////////////////////////////////////////////////////// + // Register properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name") + .set(get_rx_id().to_pp_string()); + this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists + BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){ + this->get_rx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&tvrx::set_gain, this, _1, name)); + this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(get_tvrx_gain_ranges()[name]); + } + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&tvrx::set_freq, this, _1)); + this->get_rx_subtree()->create<meta_range_t>("freq/range") + .set(tvrx_freq_range); + this->get_rx_subtree()->create<std::string>("antenna/value") + .set(tvrx_antennas.at(0)); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(tvrx_antennas); + this->get_rx_subtree()->create<std::string>("connection") + .set("I"); + this->get_rx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + this->get_rx_subtree()->create<double>("bandwidth/value") + .set(6.0e6); + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(6.0e6, 6.0e6)); + + //enable only the clocks we need + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + + //set the gpio directions and atr controls (identically) + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr + if (this->get_iface()->get_special_props().soft_clock_divider){ + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x1); // GPIO0 is clock + } + else{ + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs + } + + //send initial register settings if necessary + + //set default freq + _lo_freq = tvrx_freq_range.start() + tvrx_if_freq; //init _lo_freq to a sane default + this->get_rx_subtree()->access<double>("freq/value").set(tvrx_freq_range.start()); + + //set default gains + BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){ + this->get_rx_subtree()->access<double>("gains/"+name+"/value") + .set(get_tvrx_gain_ranges()[name].start()); + } +} + +tvrx::~tvrx(void){ +} + +/*! Return a string corresponding to the relevant band + * \param freq the frequency of interest + * \return a string corresponding to the band + */ + +static std::string get_band(double freq) { + BOOST_FOREACH(const std::string &band, tvrx_freq_ranges.keys()) { + if(freq >= tvrx_freq_ranges[band].start() && freq <= tvrx_freq_ranges[band].stop()){ + UHD_LOGV(often) << "Band: " << band << std::endl; + return band; + } + } + UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Execute a linear interpolation to find the voltage corresponding to a desired gain + * \param gain the desired gain in dB + * \param db_vector the vector of dB readings + * \param volts_vector the corresponding vector of voltages db_vector was sampled at + * \return a voltage to feed the TVRX analog gain + */ + +static double gain_interp(double gain, boost::array<double, 17> db_vector, boost::array<double, 17> volts_vector) { + double volts; + gain = uhd::clip<double>(gain, db_vector.front(), db_vector.back()); //let's not get carried away here + + boost::uint8_t gain_step = 0; + //find which bin we're in + for(size_t i = 0; i < db_vector.size()-1; i++) { + if(gain >= db_vector[i] && gain <= db_vector[i+1]) gain_step = i; + } + + //find the current slope for linear interpolation + double slope = (volts_vector[gain_step + 1] - volts_vector[gain_step]) + / (db_vector[gain_step + 1] - db_vector[gain_step]); + + //the problem here is that for gains approaching the maximum, the voltage slope becomes infinite + //i.e., a small change in gain requires an infinite change in voltage + //to cope, we limit the slope + + if(slope == std::numeric_limits<double>::infinity()) + return volts_vector[gain_step]; + + //use the volts per dB slope to find the final interpolated voltage + volts = volts_vector[gain_step] + (slope * (gain - db_vector[gain_step])); + + UHD_LOGV(often) << "Gain interp: gain: " << gain << ", gain_step: " << int(gain_step) << ", slope: " << slope << ", volts: " << volts << std::endl; + + return volts; +} + +/*! + * Convert a requested gain for the RF gain into a DAC voltage. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ + +static double rf_gain_to_voltage(double gain, double lo_freq){ + //clip the input + gain = get_tvrx_gain_ranges()["RF"].clip(gain); + + //first we need to find out what band we're in, because gains are different across different bands + std::string band = get_band(lo_freq - tvrx_if_freq); + + //this is the voltage at the TVRX gain input + double gain_volts = gain_interp(gain, tvrx_rf_gains_db[band], tvrx_gains_volts); + //this is the voltage at the USRP DAC output + double dac_volts = gain_volts / opamp_gain; + + dac_volts = uhd::clip<double>(dac_volts, 0.0, 3.3); + + UHD_LOGV(often) << boost::format( + "tvrx RF AGC gain: %f dB, dac_volts: %f V" + ) % gain % dac_volts << std::endl; + + return dac_volts; +} + +/*! + * Convert a requested gain for the IF gain into a DAC voltage. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ + +static double if_gain_to_voltage(double gain){ + //clip the input + gain = get_tvrx_gain_ranges()["IF"].clip(gain); + + double gain_volts = gain_interp(gain, tvrx_if_gains_db, tvrx_gains_volts); + double dac_volts = gain_volts / opamp_gain; + + dac_volts = uhd::clip<double>(dac_volts, 0.0, 3.3); + + UHD_LOGV(often) << boost::format( + "tvrx IF AGC gain: %f dB, dac_volts: %f V" + ) % gain % dac_volts << std::endl; + + return dac_volts; +} + +double tvrx::set_gain(double gain, const std::string &name){ + assert_has(get_tvrx_gain_ranges().keys(), name, "tvrx gain name"); + if (name == "RF"){ + this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_B, rf_gain_to_voltage(gain, _lo_freq)); + } + else if(name == "IF"){ + this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, if_gain_to_voltage(gain)); + } + else UHD_THROW_INVALID_CODE_PATH(); + _gains[name] = gain; + + return gain; +} + +/*! + * Set the tuner to center the desired frequency at 43.75MHz + * \param freq the requested frequency + */ + +double tvrx::set_freq(double freq) { + freq = tvrx_freq_range.clip(freq); + std::string prev_band = get_band(_lo_freq - tvrx_if_freq); + std::string new_band = get_band(freq); + + double target_lo_freq = freq + tvrx_if_freq; //the desired LO freq for high-side mixing + double f_ref = reference_freq / double(reference_divider); //your tuning step size + + int divisor = int((target_lo_freq + (f_ref * 4.0)) / (f_ref * 8)); //the divisor we'll use + double actual_lo_freq = (f_ref * 8 * divisor); //the LO freq we'll actually get + + if((divisor & ~0x7fff)) UHD_THROW_INVALID_CODE_PATH(); + + //now we update the registers + _tuner_4937di5_regs.db1 = (divisor >> 8) & 0xff; + _tuner_4937di5_regs.db2 = divisor & 0xff; + + if(new_band == "VHFLO") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFLO; + else if(new_band == "VHFHI") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFHI; + else if(new_band == "UHF") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_UHF; + else UHD_THROW_INVALID_CODE_PATH(); + + _tuner_4937di5_regs.power = tuner_4937di5_regs_t::POWER_OFF; + update_regs(); + + //ok don't forget to reset RF gain here if the new band != the old band + //we do this because the gains are different for different band settings + //not FAR off, but we do this to be consistent + if(prev_band != new_band) set_gain(_gains["RF"], "RF"); + + UHD_LOGV(often) << boost::format("set_freq: target LO: %f f_ref: %f divisor: %i actual LO: %f") % target_lo_freq % f_ref % divisor % actual_lo_freq << std::endl; + + _lo_freq = actual_lo_freq; //for rx props + + //Check the the IF if larger than the dsp rate and apply a corrective adjustment + //so that the cordic will be tuned to a possible rate within its range. + const double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); + if (tvrx_if_freq >= codec_rate/2){ + return _lo_freq - codec_rate; + } + + return _lo_freq; +} diff --git a/host/lib/usrp/dboard/db_tvrx2.cpp b/host/lib/usrp/dboard/db_tvrx2.cpp new file mode 100644 index 000000000..628221527 --- /dev/null +++ b/host/lib/usrp/dboard/db_tvrx2.cpp @@ -0,0 +1,1844 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Common IO Pins +#define REFCLOCK_DIV_MASK ((1 << 8)|(1 << 9)|(1 << 10)) // Three GPIO lines to CPLD for Clock Divisor Selection +#define REFCLOCK_DIV8 ((1 << 8)|(1 << 9)|(1 << 10)) // GPIO to set clock div8 mode +#define REFCLOCK_DIV7 ((0 << 8)|(1 << 9)|(1 << 10)) // GPIO to set clock div7 mode +#define REFCLOCK_DIV6 ((1 << 8)|(0 << 9)|(1 << 10)) // GPIO to set clock div6 mode +#define REFCLOCK_DIV5 ((0 << 8)|(0 << 9)|(1 << 10)) // GPIO to set clock div5 mode +#define REFCLOCK_DIV4 ((1 << 8)|(1 <<9)) // GPIO to set clock div4 mode +#define REFCLOCK_DIV3 (1 <<9) // GPIO to set clock div3 mode +#define REFCLOCK_DIV2 (1 <<8) // GPIO to set clock div2 mode +#define REFCLOCK_DIV1 ((0 << 8)|(0 << 9)|(0 << 10)) // GPIO to set clock div1 mode + +// RX1 IO Pins +#define RX1_MASTERSYNC (1 << 3) // MASTERSYNC Signal for Slave Tuner Coordination +#define RX1_FREEZE (1 << 2) // FREEZE Signal for Slave Tuner Coordination +#define RX1_IRQ (1 << 1) // IRQ Signals TDA18272HNM State Machine Completion +#define RX1_VSYNC (1 << 0) // VSYNC Pulse for AGC Holdoff + +// RX2 IO Pins +#define RX2_MASTERSYNC (1 << 7) // MASTERSYNC Signal for Slave Tuner Coordination +#define RX2_FREEZE (1 << 6) // FREEZE Signal for Slave Tuner Coordination +#define RX2_IRQ (1 << 5) // IRQ Signals TDA18272HNM State Machine Completion +#define RX2_VSYNC (1 << 4) // VSYNC Pulse for AGC Holdoff + +// Pin functions +#define RX1_OUTPUT_MASK (0) +#define RX1_INPUT_MASK (RX1_VSYNC|RX1_MASTERSYNC|RX1_FREEZE|RX1_IRQ) + +#define RX2_OUTPUT_MASK (0) +#define RX2_INPUT_MASK (RX2_VSYNC|RX2_MASTERSYNC|RX2_FREEZE|RX2_IRQ) + +#define OUTPUT_MASK (RX1_OUTPUT_MASK|RX2_OUTPUT_MASK|REFCLOCK_DIV_MASK) +#define INPUT_MASK (RX1_INPUT_MASK|RX2_INPUT_MASK) + + +#include "tda18272hnm_regs.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/array.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The TVRX2 types + **********************************************************************/ +struct tvrx2_tda18272_rfcal_result_t { + boost::int8_t delta_c; + boost::int8_t c_offset; + tvrx2_tda18272_rfcal_result_t(void): delta_c(0), c_offset(0){} +}; + +struct tvrx2_tda18272_rfcal_coeffs_t { + boost::uint8_t cal_number; + boost::int32_t RF_A1; + boost::int32_t RF_B1; + tvrx2_tda18272_rfcal_coeffs_t(void): cal_number(0), RF_A1(0), RF_B1(0) {} + tvrx2_tda18272_rfcal_coeffs_t(boost::uint32_t num): RF_A1(0), RF_B1(0) { cal_number = num; } +}; + +struct tvrx2_tda18272_cal_map_t { + boost::array<boost::uint32_t, 4> cal_freq; + boost::array<boost::uint8_t, 4> c_offset; + tvrx2_tda18272_cal_map_t(boost::array<boost::uint32_t, 4> freqs, boost::array<boost::uint8_t, 4> offsets) + { cal_freq = freqs; c_offset = offsets; } +}; + +struct tvrx2_tda18272_freq_map_t { + boost::uint32_t rf_max; + boost::uint8_t c_prog; + boost::uint8_t gain_taper; + boost::uint8_t rf_band; + tvrx2_tda18272_freq_map_t( boost::uint32_t max, boost::uint8_t c, boost::uint8_t taper, boost::uint8_t band) + { rf_max = max; c_prog = c; gain_taper = taper; rf_band = band; } +}; + +/*********************************************************************** + * The TVRX2 constants + **********************************************************************/ + +static const boost::array<freq_range_t, 4> tvrx2_tda18272_rf_bands = list_of + ( freq_range_t( 44.056e6, 144.408e6) ) + ( freq_range_t( 145.432e6, 361.496e6) ) + ( freq_range_t( 365.592e6, 618.520e6) ) + ( freq_range_t( 619.544e6, 865.304e6) ) +; + +#define TVRX2_TDA18272_FREQ_MAP_ENTRIES (565) + +static const uhd::dict<boost::uint32_t, tvrx2_tda18272_cal_map_t> tvrx2_tda18272_cal_map = map_list_of + ( 0, tvrx2_tda18272_cal_map_t( list_of( 44032000)( 48128000)( 52224000)( 56320000), list_of(15)( 0)(10)(17) ) ) + ( 1, tvrx2_tda18272_cal_map_t( list_of( 84992000)( 89088000)( 93184000)( 97280000), list_of( 1)( 0)(-2)( 3) ) ) + ( 2, tvrx2_tda18272_cal_map_t( list_of(106496000)(111616000)(115712000)(123904000), list_of( 0)(-1)( 1)( 2) ) ) + ( 3, tvrx2_tda18272_cal_map_t( list_of(161792000)(165888000)(169984000)(174080000), list_of( 3)( 0)( 1)( 2) ) ) + ( 4, tvrx2_tda18272_cal_map_t( list_of(224256000)(228352000)(232448000)(235520000), list_of( 3)( 0)( 1)( 2) ) ) + ( 5, tvrx2_tda18272_cal_map_t( list_of(301056000)(312320000)(322560000)(335872000), list_of( 0)(-1)( 1)( 2) ) ) + ( 6, tvrx2_tda18272_cal_map_t( list_of(389120000)(393216000)(397312000)(401408000), list_of(-2)( 0)(-1)( 1) ) ) + ( 7, tvrx2_tda18272_cal_map_t( list_of(455680000)(460800000)(465920000)(471040000), list_of( 0)(-2)(-3)( 1) ) ) + ( 8, tvrx2_tda18272_cal_map_t( list_of(555008000)(563200000)(570368000)(577536000), list_of(-1)( 0)(-3)(-2) ) ) + ( 9, tvrx2_tda18272_cal_map_t( list_of(647168000)(652288000)(658432000)(662528000), list_of(-6)(-3)( 0)(-5) ) ) + ( 10, tvrx2_tda18272_cal_map_t( list_of(748544000)(755712000)(762880000)(770048000), list_of(-6)(-3)( 0)(-5) ) ) + ( 11, tvrx2_tda18272_cal_map_t( list_of(792576000)(801792000)(809984000)(818176000), list_of(-5)(-2)( 0)(-4) ) ) +; + +static const std::vector<tvrx2_tda18272_freq_map_t> tvrx2_tda18272_freq_map = list_of + ( tvrx2_tda18272_freq_map_t( 39936000, 0xFF, 0x17, 0) ) + ( tvrx2_tda18272_freq_map_t( 40960000, 0xFD, 0x17, 0) ) + ( tvrx2_tda18272_freq_map_t( 41984000, 0xF1, 0x15, 0) ) + ( tvrx2_tda18272_freq_map_t( 43008000, 0xE5, 0x13, 0) ) + ( tvrx2_tda18272_freq_map_t( 44032000, 0xDB, 0x13, 0) ) + ( tvrx2_tda18272_freq_map_t( 45056000, 0xD1, 0x12, 0) ) + ( tvrx2_tda18272_freq_map_t( 46080000, 0xC7, 0x10, 0) ) + ( tvrx2_tda18272_freq_map_t( 47104000, 0xBE, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 48128000, 0xB5, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 49152000, 0xAD, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 50176000, 0xA6, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 51200000, 0x9F, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 52224000, 0x98, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 53248000, 0x91, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 54272000, 0x8B, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 55296000, 0x86, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 56320000, 0x80, 0x0F, 0) ) + ( tvrx2_tda18272_freq_map_t( 57344000, 0x7B, 0x0E, 0) ) + ( tvrx2_tda18272_freq_map_t( 58368000, 0x76, 0x0E, 0) ) + ( tvrx2_tda18272_freq_map_t( 59392000, 0x72, 0x0D, 0) ) + ( tvrx2_tda18272_freq_map_t( 60416000, 0x6D, 0x0D, 0) ) + ( tvrx2_tda18272_freq_map_t( 61440000, 0x69, 0x0C, 0) ) + ( tvrx2_tda18272_freq_map_t( 62464000, 0x65, 0x0C, 0) ) + ( tvrx2_tda18272_freq_map_t( 63488000, 0x61, 0x0B, 0) ) + ( tvrx2_tda18272_freq_map_t( 64512000, 0x5E, 0x0B, 0) ) + ( tvrx2_tda18272_freq_map_t( 64512000, 0x5A, 0x0B, 0) ) + ( tvrx2_tda18272_freq_map_t( 65536000, 0x57, 0x0A, 0) ) + ( tvrx2_tda18272_freq_map_t( 66560000, 0x54, 0x0A, 0) ) + ( tvrx2_tda18272_freq_map_t( 67584000, 0x51, 0x09, 0) ) + ( tvrx2_tda18272_freq_map_t( 68608000, 0x4E, 0x09, 0) ) + ( tvrx2_tda18272_freq_map_t( 69632000, 0x4B, 0x09, 0) ) + ( tvrx2_tda18272_freq_map_t( 70656000, 0x49, 0x08, 0) ) + ( tvrx2_tda18272_freq_map_t( 71680000, 0x46, 0x08, 0) ) + ( tvrx2_tda18272_freq_map_t( 72704000, 0x44, 0x08, 0) ) + ( tvrx2_tda18272_freq_map_t( 73728000, 0x41, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 74752000, 0x3F, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 75776000, 0x3D, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 76800000, 0x3B, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 77824000, 0x39, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 78848000, 0x37, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 79872000, 0x35, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 80896000, 0x33, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 81920000, 0x32, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 82944000, 0x30, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 83968000, 0x2F, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 84992000, 0x2D, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 86016000, 0x2C, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 87040000, 0x2A, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t( 88064000, 0x29, 0x06, 0) ) + ( tvrx2_tda18272_freq_map_t( 89088000, 0x27, 0x06, 0) ) + ( tvrx2_tda18272_freq_map_t( 90112000, 0x26, 0x06, 0) ) + ( tvrx2_tda18272_freq_map_t( 91136000, 0x25, 0x06, 0) ) + ( tvrx2_tda18272_freq_map_t( 92160000, 0x24, 0x06, 0) ) + ( tvrx2_tda18272_freq_map_t( 93184000, 0x22, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t( 94208000, 0x21, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t( 95232000, 0x20, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t( 96256000, 0x1F, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t( 97280000, 0x1E, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t( 98304000, 0x1D, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t( 99328000, 0x1C, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(100352000, 0x1B, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(101376000, 0x1A, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(103424000, 0x19, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(104448000, 0x18, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(105472000, 0x17, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(106496000, 0x16, 0x03, 0) ) + ( tvrx2_tda18272_freq_map_t(106496000, 0x15, 0x03, 0) ) + ( tvrx2_tda18272_freq_map_t(108544000, 0x14, 0x03, 0) ) + ( tvrx2_tda18272_freq_map_t(109568000, 0x13, 0x03, 0) ) + ( tvrx2_tda18272_freq_map_t(111616000, 0x12, 0x03, 0) ) + ( tvrx2_tda18272_freq_map_t(112640000, 0x11, 0x03, 0) ) + ( tvrx2_tda18272_freq_map_t(113664000, 0x11, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t(114688000, 0x10, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t(115712000, 0x0F, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t(117760000, 0x0E, 0x07, 0) ) + ( tvrx2_tda18272_freq_map_t(119808000, 0x0D, 0x06, 0) ) + ( tvrx2_tda18272_freq_map_t(121856000, 0x0C, 0x06, 0) ) + ( tvrx2_tda18272_freq_map_t(123904000, 0x0B, 0x06, 0) ) + ( tvrx2_tda18272_freq_map_t(125952000, 0x0A, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t(128000000, 0x09, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t(130048000, 0x08, 0x05, 0) ) + ( tvrx2_tda18272_freq_map_t(133120000, 0x07, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(135168000, 0x06, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(138240000, 0x05, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(141312000, 0x04, 0x04, 0) ) + ( tvrx2_tda18272_freq_map_t(144384000, 0x03, 0x03, 0) ) + ( tvrx2_tda18272_freq_map_t(145408000, 0xE0, 0x3F, 1) ) + ( tvrx2_tda18272_freq_map_t(147456000, 0xDC, 0x37, 1) ) + ( tvrx2_tda18272_freq_map_t(148480000, 0xD9, 0x32, 1) ) + ( tvrx2_tda18272_freq_map_t(149504000, 0xD6, 0x2F, 1) ) + ( tvrx2_tda18272_freq_map_t(149504000, 0xD2, 0x2F, 1) ) + ( tvrx2_tda18272_freq_map_t(150528000, 0xCF, 0x2F, 1) ) + ( tvrx2_tda18272_freq_map_t(151552000, 0xCC, 0x2B, 1) ) + ( tvrx2_tda18272_freq_map_t(152576000, 0xC9, 0x27, 1) ) + ( tvrx2_tda18272_freq_map_t(153600000, 0xC5, 0x27, 1) ) + ( tvrx2_tda18272_freq_map_t(154624000, 0xC2, 0x25, 1) ) + ( tvrx2_tda18272_freq_map_t(155648000, 0xBF, 0x23, 1) ) + ( tvrx2_tda18272_freq_map_t(156672000, 0xBD, 0x20, 1) ) + ( tvrx2_tda18272_freq_map_t(157696000, 0xBA, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(158720000, 0xB7, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(159744000, 0xB4, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(160768000, 0xB1, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(161792000, 0xAF, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(162816000, 0xAC, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(163840000, 0xAA, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(164864000, 0xA7, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(165888000, 0xA5, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(166912000, 0xA2, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(167936000, 0xA0, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(168960000, 0x9D, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(169984000, 0x9B, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(171008000, 0x99, 0x1F, 1) ) + ( tvrx2_tda18272_freq_map_t(172032000, 0x97, 0x1E, 1) ) + ( tvrx2_tda18272_freq_map_t(173056000, 0x95, 0x1D, 1) ) + ( tvrx2_tda18272_freq_map_t(174080000, 0x92, 0x1C, 1) ) + ( tvrx2_tda18272_freq_map_t(175104000, 0x90, 0x1B, 1) ) + ( tvrx2_tda18272_freq_map_t(176128000, 0x8E, 0x1A, 1) ) + ( tvrx2_tda18272_freq_map_t(177152000, 0x8C, 0x19, 1) ) + ( tvrx2_tda18272_freq_map_t(178176000, 0x8A, 0x18, 1) ) + ( tvrx2_tda18272_freq_map_t(179200000, 0x88, 0x17, 1) ) + ( tvrx2_tda18272_freq_map_t(180224000, 0x86, 0x17, 1) ) + ( tvrx2_tda18272_freq_map_t(181248000, 0x84, 0x17, 1) ) + ( tvrx2_tda18272_freq_map_t(182272000, 0x82, 0x17, 1) ) + ( tvrx2_tda18272_freq_map_t(183296000, 0x81, 0x17, 1) ) + ( tvrx2_tda18272_freq_map_t(184320000, 0x7F, 0x17, 1) ) + ( tvrx2_tda18272_freq_map_t(185344000, 0x7D, 0x16, 1) ) + ( tvrx2_tda18272_freq_map_t(186368000, 0x7B, 0x15, 1) ) + ( tvrx2_tda18272_freq_map_t(187392000, 0x7A, 0x14, 1) ) + ( tvrx2_tda18272_freq_map_t(188416000, 0x78, 0x14, 1) ) + ( tvrx2_tda18272_freq_map_t(189440000, 0x76, 0x13, 1) ) + ( tvrx2_tda18272_freq_map_t(190464000, 0x75, 0x13, 1) ) + ( tvrx2_tda18272_freq_map_t(191488000, 0x73, 0x13, 1) ) + ( tvrx2_tda18272_freq_map_t(192512000, 0x71, 0x12, 1) ) + ( tvrx2_tda18272_freq_map_t(192512000, 0x70, 0x11, 1) ) + ( tvrx2_tda18272_freq_map_t(193536000, 0x6E, 0x11, 1) ) + ( tvrx2_tda18272_freq_map_t(194560000, 0x6D, 0x10, 1) ) + ( tvrx2_tda18272_freq_map_t(195584000, 0x6B, 0x10, 1) ) + ( tvrx2_tda18272_freq_map_t(196608000, 0x6A, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(197632000, 0x68, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(198656000, 0x67, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(199680000, 0x65, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(200704000, 0x64, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(201728000, 0x63, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(202752000, 0x61, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(203776000, 0x60, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(204800000, 0x5F, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(205824000, 0x5D, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(206848000, 0x5C, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(207872000, 0x5B, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(208896000, 0x5A, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(209920000, 0x58, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(210944000, 0x57, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(211968000, 0x56, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(212992000, 0x55, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(214016000, 0x54, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(215040000, 0x53, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(216064000, 0x52, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(217088000, 0x50, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(218112000, 0x4F, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(219136000, 0x4E, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(220160000, 0x4D, 0x0E, 1) ) + ( tvrx2_tda18272_freq_map_t(221184000, 0x4C, 0x0E, 1) ) + ( tvrx2_tda18272_freq_map_t(222208000, 0x4B, 0x0E, 1) ) + ( tvrx2_tda18272_freq_map_t(223232000, 0x4A, 0x0E, 1) ) + ( tvrx2_tda18272_freq_map_t(224256000, 0x49, 0x0D, 1) ) + ( tvrx2_tda18272_freq_map_t(225280000, 0x48, 0x0D, 1) ) + ( tvrx2_tda18272_freq_map_t(226304000, 0x47, 0x0D, 1) ) + ( tvrx2_tda18272_freq_map_t(227328000, 0x46, 0x0D, 1) ) + ( tvrx2_tda18272_freq_map_t(228352000, 0x45, 0x0C, 1) ) + ( tvrx2_tda18272_freq_map_t(229376000, 0x44, 0x0C, 1) ) + ( tvrx2_tda18272_freq_map_t(230400000, 0x43, 0x0C, 1) ) + ( tvrx2_tda18272_freq_map_t(231424000, 0x42, 0x0C, 1) ) + ( tvrx2_tda18272_freq_map_t(232448000, 0x42, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(233472000, 0x41, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(234496000, 0x40, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(234496000, 0x3F, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(235520000, 0x3E, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(236544000, 0x3D, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(237568000, 0x3C, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(239616000, 0x3B, 0x0A, 1) ) + ( tvrx2_tda18272_freq_map_t(240640000, 0x3A, 0x0A, 1) ) + ( tvrx2_tda18272_freq_map_t(241664000, 0x39, 0x0A, 1) ) + ( tvrx2_tda18272_freq_map_t(242688000, 0x38, 0x0A, 1) ) + ( tvrx2_tda18272_freq_map_t(244736000, 0x37, 0x09, 1) ) + ( tvrx2_tda18272_freq_map_t(245760000, 0x36, 0x09, 1) ) + ( tvrx2_tda18272_freq_map_t(246784000, 0x35, 0x09, 1) ) + ( tvrx2_tda18272_freq_map_t(248832000, 0x34, 0x09, 1) ) + ( tvrx2_tda18272_freq_map_t(249856000, 0x33, 0x09, 1) ) + ( tvrx2_tda18272_freq_map_t(250880000, 0x32, 0x08, 1) ) + ( tvrx2_tda18272_freq_map_t(252928000, 0x31, 0x08, 1) ) + ( tvrx2_tda18272_freq_map_t(253952000, 0x30, 0x08, 1) ) + ( tvrx2_tda18272_freq_map_t(256000000, 0x2F, 0x08, 1) ) + ( tvrx2_tda18272_freq_map_t(257024000, 0x2E, 0x08, 1) ) + ( tvrx2_tda18272_freq_map_t(259072000, 0x2D, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(260096000, 0x2C, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(262144000, 0x2B, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(264192000, 0x2A, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(265216000, 0x29, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(267264000, 0x28, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(269312000, 0x27, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(270336000, 0x26, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(272384000, 0x25, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(274432000, 0x24, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(276480000, 0x23, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(277504000, 0x22, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(279552000, 0x21, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(281600000, 0x20, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(283648000, 0x1F, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(285696000, 0x1E, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(287744000, 0x1D, 0x0F, 1) ) + ( tvrx2_tda18272_freq_map_t(289792000, 0x1C, 0x0E, 1) ) + ( tvrx2_tda18272_freq_map_t(291840000, 0x1B, 0x0E, 1) ) + ( tvrx2_tda18272_freq_map_t(293888000, 0x1A, 0x0D, 1) ) + ( tvrx2_tda18272_freq_map_t(296960000, 0x19, 0x0D, 1) ) + ( tvrx2_tda18272_freq_map_t(299008000, 0x18, 0x0C, 1) ) + ( tvrx2_tda18272_freq_map_t(301056000, 0x17, 0x0C, 1) ) + ( tvrx2_tda18272_freq_map_t(304128000, 0x16, 0x0C, 1) ) + ( tvrx2_tda18272_freq_map_t(306176000, 0x15, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(309248000, 0x14, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(312320000, 0x13, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(314368000, 0x12, 0x0B, 1) ) + ( tvrx2_tda18272_freq_map_t(317440000, 0x11, 0x0A, 1) ) + ( tvrx2_tda18272_freq_map_t(320512000, 0x10, 0x0A, 1) ) + ( tvrx2_tda18272_freq_map_t(322560000, 0x0F, 0x0A, 1) ) + ( tvrx2_tda18272_freq_map_t(325632000, 0x0E, 0x09, 1) ) + ( tvrx2_tda18272_freq_map_t(328704000, 0x0D, 0x09, 1) ) + ( tvrx2_tda18272_freq_map_t(331776000, 0x0C, 0x08, 1) ) + ( tvrx2_tda18272_freq_map_t(335872000, 0x0B, 0x08, 1) ) + ( tvrx2_tda18272_freq_map_t(338944000, 0x0A, 0x08, 1) ) + ( tvrx2_tda18272_freq_map_t(343040000, 0x09, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(346112000, 0x08, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(350208000, 0x07, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(354304000, 0x06, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(358400000, 0x05, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(362496000, 0x04, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(365568000, 0x04, 0x07, 1) ) + ( tvrx2_tda18272_freq_map_t(367616000, 0xDA, 0x2A, 2) ) + ( tvrx2_tda18272_freq_map_t(367616000, 0xD9, 0x27, 2) ) + ( tvrx2_tda18272_freq_map_t(368640000, 0xD8, 0x27, 2) ) + ( tvrx2_tda18272_freq_map_t(369664000, 0xD6, 0x27, 2) ) + ( tvrx2_tda18272_freq_map_t(370688000, 0xD5, 0x27, 2) ) + ( tvrx2_tda18272_freq_map_t(371712000, 0xD3, 0x25, 2) ) + ( tvrx2_tda18272_freq_map_t(372736000, 0xD2, 0x23, 2) ) + ( tvrx2_tda18272_freq_map_t(373760000, 0xD0, 0x23, 2) ) + ( tvrx2_tda18272_freq_map_t(374784000, 0xCF, 0x21, 2) ) + ( tvrx2_tda18272_freq_map_t(375808000, 0xCD, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(376832000, 0xCC, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(377856000, 0xCA, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(378880000, 0xC9, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(379904000, 0xC7, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(380928000, 0xC6, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(381952000, 0xC4, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(382976000, 0xC3, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(384000000, 0xC1, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(385024000, 0xC0, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(386048000, 0xBF, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(387072000, 0xBD, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(388096000, 0xBC, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(389120000, 0xBB, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(390144000, 0xB9, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(391168000, 0xB8, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(392192000, 0xB7, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(393216000, 0xB5, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(394240000, 0xB4, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(395264000, 0xB3, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(396288000, 0xB1, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(397312000, 0xB0, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(398336000, 0xAF, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(399360000, 0xAD, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(400384000, 0xAC, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(401408000, 0xAB, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(402432000, 0xAA, 0x1F, 2) ) + ( tvrx2_tda18272_freq_map_t(403456000, 0xA8, 0x1E, 2) ) + ( tvrx2_tda18272_freq_map_t(404480000, 0xA7, 0x1D, 2) ) + ( tvrx2_tda18272_freq_map_t(405504000, 0xA6, 0x1D, 2) ) + ( tvrx2_tda18272_freq_map_t(405504000, 0xA5, 0x1C, 2) ) + ( tvrx2_tda18272_freq_map_t(406528000, 0xA3, 0x1C, 2) ) + ( tvrx2_tda18272_freq_map_t(407552000, 0xA2, 0x1B, 2) ) + ( tvrx2_tda18272_freq_map_t(408576000, 0xA1, 0x1B, 2) ) + ( tvrx2_tda18272_freq_map_t(409600000, 0xA0, 0x1B, 2) ) + ( tvrx2_tda18272_freq_map_t(410624000, 0x9F, 0x1A, 2) ) + ( tvrx2_tda18272_freq_map_t(411648000, 0x9D, 0x1A, 2) ) + ( tvrx2_tda18272_freq_map_t(412672000, 0x9C, 0x19, 2) ) + ( tvrx2_tda18272_freq_map_t(413696000, 0x9B, 0x18, 2) ) + ( tvrx2_tda18272_freq_map_t(414720000, 0x9A, 0x18, 2) ) + ( tvrx2_tda18272_freq_map_t(415744000, 0x99, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(416768000, 0x98, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(417792000, 0x97, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(418816000, 0x95, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(419840000, 0x94, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(420864000, 0x93, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(421888000, 0x92, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(422912000, 0x91, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(423936000, 0x90, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(424960000, 0x8F, 0x17, 2) ) + ( tvrx2_tda18272_freq_map_t(425984000, 0x8E, 0x16, 2) ) + ( tvrx2_tda18272_freq_map_t(427008000, 0x8D, 0x16, 2) ) + ( tvrx2_tda18272_freq_map_t(428032000, 0x8C, 0x15, 2) ) + ( tvrx2_tda18272_freq_map_t(429056000, 0x8B, 0x15, 2) ) + ( tvrx2_tda18272_freq_map_t(430080000, 0x8A, 0x15, 2) ) + ( tvrx2_tda18272_freq_map_t(431104000, 0x88, 0x14, 2) ) + ( tvrx2_tda18272_freq_map_t(432128000, 0x87, 0x14, 2) ) + ( tvrx2_tda18272_freq_map_t(433152000, 0x86, 0x14, 2) ) + ( tvrx2_tda18272_freq_map_t(434176000, 0x85, 0x13, 2) ) + ( tvrx2_tda18272_freq_map_t(435200000, 0x84, 0x13, 2) ) + ( tvrx2_tda18272_freq_map_t(436224000, 0x83, 0x13, 2) ) + ( tvrx2_tda18272_freq_map_t(437248000, 0x82, 0x13, 2) ) + ( tvrx2_tda18272_freq_map_t(438272000, 0x81, 0x13, 2) ) + ( tvrx2_tda18272_freq_map_t(439296000, 0x80, 0x12, 2) ) + ( tvrx2_tda18272_freq_map_t(440320000, 0x7F, 0x12, 2) ) + ( tvrx2_tda18272_freq_map_t(441344000, 0x7E, 0x12, 2) ) + ( tvrx2_tda18272_freq_map_t(442368000, 0x7D, 0x11, 2) ) + ( tvrx2_tda18272_freq_map_t(444416000, 0x7C, 0x11, 2) ) + ( tvrx2_tda18272_freq_map_t(445440000, 0x7B, 0x10, 2) ) + ( tvrx2_tda18272_freq_map_t(446464000, 0x7A, 0x10, 2) ) + ( tvrx2_tda18272_freq_map_t(447488000, 0x79, 0x10, 2) ) + ( tvrx2_tda18272_freq_map_t(448512000, 0x78, 0x10, 2) ) + ( tvrx2_tda18272_freq_map_t(448512000, 0x77, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(449536000, 0x76, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(450560000, 0x75, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(451584000, 0x74, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(452608000, 0x73, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(453632000, 0x72, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(454656000, 0x71, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(455680000, 0x70, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(457728000, 0x6F, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(458752000, 0x6E, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(459776000, 0x6D, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(460800000, 0x6C, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(461824000, 0x6B, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(462848000, 0x6A, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(464896000, 0x69, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(465920000, 0x68, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(466944000, 0x67, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(467968000, 0x66, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(468992000, 0x65, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(471040000, 0x64, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(472064000, 0x63, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(473088000, 0x62, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(474112000, 0x61, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(476160000, 0x60, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(477184000, 0x5F, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(478208000, 0x5E, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(479232000, 0x5D, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(481280000, 0x5C, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(482304000, 0x5B, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(483328000, 0x5A, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(485376000, 0x59, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(486400000, 0x58, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(487424000, 0x57, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(489472000, 0x56, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(490496000, 0x55, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(490496000, 0x54, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(492544000, 0x53, 0x0E, 2) ) + ( tvrx2_tda18272_freq_map_t(493568000, 0x52, 0x0E, 2) ) + ( tvrx2_tda18272_freq_map_t(495616000, 0x51, 0x0E, 2) ) + ( tvrx2_tda18272_freq_map_t(496640000, 0x50, 0x0E, 2) ) + ( tvrx2_tda18272_freq_map_t(497664000, 0x4F, 0x0E, 2) ) + ( tvrx2_tda18272_freq_map_t(499712000, 0x4E, 0x0D, 2) ) + ( tvrx2_tda18272_freq_map_t(500736000, 0x4D, 0x0D, 2) ) + ( tvrx2_tda18272_freq_map_t(502784000, 0x4C, 0x0D, 2) ) + ( tvrx2_tda18272_freq_map_t(503808000, 0x4B, 0x0D, 2) ) + ( tvrx2_tda18272_freq_map_t(505856000, 0x4A, 0x0C, 2) ) + ( tvrx2_tda18272_freq_map_t(506880000, 0x49, 0x0C, 2) ) + ( tvrx2_tda18272_freq_map_t(508928000, 0x48, 0x0C, 2) ) + ( tvrx2_tda18272_freq_map_t(509952000, 0x47, 0x0C, 2) ) + ( tvrx2_tda18272_freq_map_t(512000000, 0x46, 0x0C, 2) ) + ( tvrx2_tda18272_freq_map_t(513024000, 0x45, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(515072000, 0x44, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(517120000, 0x43, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(518144000, 0x42, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(520192000, 0x41, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(521216000, 0x40, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(523264000, 0x3F, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(525312000, 0x3E, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(526336000, 0x3D, 0x0B, 2) ) + ( tvrx2_tda18272_freq_map_t(528384000, 0x3C, 0x0A, 2) ) + ( tvrx2_tda18272_freq_map_t(530432000, 0x3B, 0x0A, 2) ) + ( tvrx2_tda18272_freq_map_t(531456000, 0x3A, 0x0A, 2) ) + ( tvrx2_tda18272_freq_map_t(533504000, 0x39, 0x0A, 2) ) + ( tvrx2_tda18272_freq_map_t(534528000, 0x38, 0x0A, 2) ) + ( tvrx2_tda18272_freq_map_t(536576000, 0x37, 0x0A, 2) ) + ( tvrx2_tda18272_freq_map_t(537600000, 0x36, 0x09, 2) ) + ( tvrx2_tda18272_freq_map_t(539648000, 0x35, 0x09, 2) ) + ( tvrx2_tda18272_freq_map_t(541696000, 0x34, 0x09, 2) ) + ( tvrx2_tda18272_freq_map_t(543744000, 0x33, 0x09, 2) ) + ( tvrx2_tda18272_freq_map_t(544768000, 0x32, 0x09, 2) ) + ( tvrx2_tda18272_freq_map_t(546816000, 0x31, 0x09, 2) ) + ( tvrx2_tda18272_freq_map_t(548864000, 0x30, 0x08, 2) ) + ( tvrx2_tda18272_freq_map_t(550912000, 0x2F, 0x08, 2) ) + ( tvrx2_tda18272_freq_map_t(552960000, 0x2E, 0x08, 2) ) + ( tvrx2_tda18272_freq_map_t(555008000, 0x2D, 0x08, 2) ) + ( tvrx2_tda18272_freq_map_t(557056000, 0x2C, 0x08, 2) ) + ( tvrx2_tda18272_freq_map_t(559104000, 0x2B, 0x08, 2) ) + ( tvrx2_tda18272_freq_map_t(561152000, 0x2A, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(563200000, 0x29, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(565248000, 0x28, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(567296000, 0x27, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(569344000, 0x26, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(570368000, 0x26, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(571392000, 0x25, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(573440000, 0x24, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(575488000, 0x23, 0x07, 2) ) + ( tvrx2_tda18272_freq_map_t(577536000, 0x22, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(578560000, 0x21, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(580608000, 0x20, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(583680000, 0x1F, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(585728000, 0x1E, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(587776000, 0x1D, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(589824000, 0x1C, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(592896000, 0x1B, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(594944000, 0x1A, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(596992000, 0x19, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(600064000, 0x18, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(602112000, 0x17, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(604160000, 0x16, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(607232000, 0x15, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(609280000, 0x14, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(612352000, 0x13, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(615424000, 0x12, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(617472000, 0x11, 0x0F, 2) ) + ( tvrx2_tda18272_freq_map_t(619520000, 0x10, 0x0E, 2) ) + ( tvrx2_tda18272_freq_map_t(621568000, 0x0F, 0x0E, 2) ) + ( tvrx2_tda18272_freq_map_t(623616000, 0x0F, 0x0E, 2) ) + ( tvrx2_tda18272_freq_map_t(624640000, 0xA3, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(625664000, 0xA2, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(626688000, 0xA1, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(627712000, 0xA0, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(628736000, 0x9F, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(630784000, 0x9E, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(631808000, 0x9D, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(632832000, 0x9C, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(633856000, 0x9B, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(635904000, 0x9A, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(636928000, 0x99, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(637952000, 0x98, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(638976000, 0x97, 0x1F, 3) ) + ( tvrx2_tda18272_freq_map_t(641024000, 0x96, 0x1E, 3) ) + ( tvrx2_tda18272_freq_map_t(642048000, 0x95, 0x1E, 3) ) + ( tvrx2_tda18272_freq_map_t(643072000, 0x94, 0x1E, 3) ) + ( tvrx2_tda18272_freq_map_t(644096000, 0x93, 0x1D, 3) ) + ( tvrx2_tda18272_freq_map_t(646144000, 0x92, 0x1D, 3) ) + ( tvrx2_tda18272_freq_map_t(647168000, 0x91, 0x1C, 3) ) + ( tvrx2_tda18272_freq_map_t(648192000, 0x90, 0x1C, 3) ) + ( tvrx2_tda18272_freq_map_t(650240000, 0x8F, 0x1B, 3) ) + ( tvrx2_tda18272_freq_map_t(651264000, 0x8E, 0x1B, 3) ) + ( tvrx2_tda18272_freq_map_t(652288000, 0x8D, 0x1B, 3) ) + ( tvrx2_tda18272_freq_map_t(654336000, 0x8C, 0x1B, 3) ) + ( tvrx2_tda18272_freq_map_t(655360000, 0x8B, 0x1B, 3) ) + ( tvrx2_tda18272_freq_map_t(656384000, 0x8A, 0x1B, 3) ) + ( tvrx2_tda18272_freq_map_t(658432000, 0x89, 0x1A, 3) ) + ( tvrx2_tda18272_freq_map_t(659456000, 0x88, 0x1A, 3) ) + ( tvrx2_tda18272_freq_map_t(660480000, 0x87, 0x1A, 3) ) + ( tvrx2_tda18272_freq_map_t(661504000, 0x86, 0x19, 3) ) + ( tvrx2_tda18272_freq_map_t(662528000, 0x85, 0x19, 3) ) + ( tvrx2_tda18272_freq_map_t(664576000, 0x84, 0x18, 3) ) + ( tvrx2_tda18272_freq_map_t(665600000, 0x83, 0x18, 3) ) + ( tvrx2_tda18272_freq_map_t(666624000, 0x82, 0x18, 3) ) + ( tvrx2_tda18272_freq_map_t(668672000, 0x81, 0x18, 3) ) + ( tvrx2_tda18272_freq_map_t(669696000, 0x80, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(671744000, 0x7F, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(672768000, 0x7E, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(674816000, 0x7D, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(675840000, 0x7C, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(676864000, 0x7B, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(678912000, 0x7A, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(679936000, 0x79, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(681984000, 0x78, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(683008000, 0x77, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(685056000, 0x76, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(686080000, 0x75, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(688128000, 0x74, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(689152000, 0x73, 0x17, 3) ) + ( tvrx2_tda18272_freq_map_t(691200000, 0x72, 0x16, 3) ) + ( tvrx2_tda18272_freq_map_t(693248000, 0x71, 0x16, 3) ) + ( tvrx2_tda18272_freq_map_t(694272000, 0x70, 0x16, 3) ) + ( tvrx2_tda18272_freq_map_t(696320000, 0x6F, 0x15, 3) ) + ( tvrx2_tda18272_freq_map_t(697344000, 0x6E, 0x15, 3) ) + ( tvrx2_tda18272_freq_map_t(699392000, 0x6D, 0x15, 3) ) + ( tvrx2_tda18272_freq_map_t(700416000, 0x6C, 0x15, 3) ) + ( tvrx2_tda18272_freq_map_t(702464000, 0x6B, 0x14, 3) ) + ( tvrx2_tda18272_freq_map_t(704512000, 0x6A, 0x14, 3) ) + ( tvrx2_tda18272_freq_map_t(704512000, 0x69, 0x14, 3) ) + ( tvrx2_tda18272_freq_map_t(706560000, 0x68, 0x14, 3) ) + ( tvrx2_tda18272_freq_map_t(707584000, 0x67, 0x13, 3) ) + ( tvrx2_tda18272_freq_map_t(709632000, 0x66, 0x13, 3) ) + ( tvrx2_tda18272_freq_map_t(711680000, 0x65, 0x13, 3) ) + ( tvrx2_tda18272_freq_map_t(712704000, 0x64, 0x13, 3) ) + ( tvrx2_tda18272_freq_map_t(714752000, 0x63, 0x13, 3) ) + ( tvrx2_tda18272_freq_map_t(716800000, 0x62, 0x13, 3) ) + ( tvrx2_tda18272_freq_map_t(717824000, 0x61, 0x13, 3) ) + ( tvrx2_tda18272_freq_map_t(719872000, 0x60, 0x13, 3) ) + ( tvrx2_tda18272_freq_map_t(721920000, 0x5F, 0x12, 3) ) + ( tvrx2_tda18272_freq_map_t(723968000, 0x5E, 0x12, 3) ) + ( tvrx2_tda18272_freq_map_t(724992000, 0x5D, 0x12, 3) ) + ( tvrx2_tda18272_freq_map_t(727040000, 0x5C, 0x12, 3) ) + ( tvrx2_tda18272_freq_map_t(729088000, 0x5B, 0x11, 3) ) + ( tvrx2_tda18272_freq_map_t(731136000, 0x5A, 0x11, 3) ) + ( tvrx2_tda18272_freq_map_t(732160000, 0x59, 0x11, 3) ) + ( tvrx2_tda18272_freq_map_t(734208000, 0x58, 0x11, 3) ) + ( tvrx2_tda18272_freq_map_t(736256000, 0x57, 0x10, 3) ) + ( tvrx2_tda18272_freq_map_t(738304000, 0x56, 0x10, 3) ) + ( tvrx2_tda18272_freq_map_t(740352000, 0x55, 0x10, 3) ) + ( tvrx2_tda18272_freq_map_t(741376000, 0x54, 0x10, 3) ) + ( tvrx2_tda18272_freq_map_t(743424000, 0x53, 0x10, 3) ) + ( tvrx2_tda18272_freq_map_t(745472000, 0x52, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(746496000, 0x51, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(748544000, 0x50, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(750592000, 0x4F, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(752640000, 0x4E, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(753664000, 0x4D, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(755712000, 0x4C, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(757760000, 0x4B, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(759808000, 0x4A, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(761856000, 0x49, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(762880000, 0x49, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(763904000, 0x48, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(765952000, 0x47, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(768000000, 0x46, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(770048000, 0x45, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(772096000, 0x44, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(774144000, 0x43, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(776192000, 0x42, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(778240000, 0x41, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(780288000, 0x40, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(783360000, 0x3F, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(785408000, 0x3E, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(787456000, 0x3D, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(789504000, 0x3C, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(790528000, 0x3B, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(792576000, 0x3A, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(794624000, 0x39, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(797696000, 0x38, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(799744000, 0x37, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(801792000, 0x36, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(803840000, 0x35, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(806912000, 0x34, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(808960000, 0x33, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(809984000, 0x33, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(811008000, 0x32, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(813056000, 0x31, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(816128000, 0x30, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(818176000, 0x2F, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(820224000, 0x2E, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(823296000, 0x2D, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(825344000, 0x2C, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(828416000, 0x2B, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(830464000, 0x2A, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(832512000, 0x29, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(834560000, 0x28, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(836608000, 0x27, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(839680000, 0x26, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(841728000, 0x25, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(844800000, 0x24, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(847872000, 0x23, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(849920000, 0x22, 0x0F, 3) ) + ( tvrx2_tda18272_freq_map_t(852992000, 0x21, 0x0E, 3) ) + ( tvrx2_tda18272_freq_map_t(855040000, 0x20, 0x0E, 3) ) + ( tvrx2_tda18272_freq_map_t(858112000, 0x1F, 0x0E, 3) ) + ( tvrx2_tda18272_freq_map_t(861184000, 0x1E, 0x0E, 3) ) + ( tvrx2_tda18272_freq_map_t(863232000, 0x1D, 0x0E, 3) ) + ( tvrx2_tda18272_freq_map_t(866304000, 0x1C, 0x0E, 3) ) + ( tvrx2_tda18272_freq_map_t(900096000, 0x10, 0x0C, 3) ) + ( tvrx2_tda18272_freq_map_t(929792000, 0x07, 0x0B, 3) ) + ( tvrx2_tda18272_freq_map_t(969728000, 0x00, 0x0A, 3) ) +; + +static const freq_range_t tvrx2_freq_range(42e6, 870e6); + +static const freq_range_t tvrx2_bandwidth_range = list_of + (range_t(1.7e6)) + (range_t(6.0e6)) + (range_t(7.0e6)) + (range_t(8.0e6)) + (range_t(10.0e6)) +; + +static const uhd::dict<std::string, std::string> tvrx2_sd_name_to_antennas = map_list_of + ("RX1", "J100") + ("RX2", "J140") +; + +static const uhd::dict<std::string, std::string> tvrx2_sd_name_to_conn = map_list_of + ("RX1", "Q") + ("RX2", "I") +; + +static const uhd::dict<std::string, boost::uint8_t> tvrx2_sd_name_to_i2c_addr = map_list_of + ("RX1", 0x63) + ("RX2", 0x60) +; + +static const uhd::dict<std::string, boost::uint8_t> tvrx2_sd_name_to_irq_io = map_list_of + ("RX1", (RX1_IRQ)) + ("RX2", (RX2_IRQ)) +; + +static const uhd::dict<std::string, dboard_iface::aux_dac_t> tvrx2_sd_name_to_dac = map_list_of + ("RX1", dboard_iface::AUX_DAC_A) + ("RX2", dboard_iface::AUX_DAC_B) +; + +static const uhd::dict<std::string, gain_range_t> tvrx2_gain_ranges = map_list_of +// ("LNA", gain_range_t(-12, 15, 3)) +// ("RF_FILTER", gain_range_t(-11, -2, 3)) +// ("IR_MIXER", gain_range_t(2, 14, 3)) +// ("LPF", gain_range_t(0, 9, 3)) + ("IF", gain_range_t(0, 30, 0.5)) +; + +/*********************************************************************** + * The TVRX2 dboard class + **********************************************************************/ +class tvrx2 : public rx_dboard_base{ +public: + tvrx2(ctor_args_t args); + ~tvrx2(void); + +private: + double _freq_scalar; + double _lo_freq; + double _if_freq; + double _bandwidth; + uhd::dict<std::string, double> _gains; + tda18272hnm_regs_t _tda18272hnm_regs; + uhd::dict<boost::uint32_t, tvrx2_tda18272_rfcal_result_t> _rfcal_results; + uhd::dict<boost::uint32_t, tvrx2_tda18272_rfcal_coeffs_t> _rfcal_coeffs; + + bool _enabled; + + bool set_enabled(bool); + + double set_lo_freq(double target_freq); + double set_gain(double gain, const std::string &name); + double set_bandwidth(double bandwidth); + + void set_scaled_rf_freq(double rf_freq); + double get_scaled_rf_freq(void); + void set_scaled_if_freq(double if_freq); + double get_scaled_if_freq(void); + void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg); + void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg); + + freq_range_t get_tda18272_rfcal_result_freq_range(boost::uint32_t result); + void tvrx2_tda18272_init_rfcal(void); + void tvrx2_tda18272_tune_rf_filter(boost::uint32_t uRF); + void soft_calibration(void); + void transition_0(void); + void transition_1(void); + void transition_2(int rf_freq); + void transition_3(void); + void transition_4(int rf_freq); + void wait_irq(void); + void test_rf_filter_robustness(void); + +/*********************************************************************** + * The TVRX2 class helper functions + **********************************************************************/ + /*! + * Is the IRQ set or cleared? + * \return true for set + */ + bool get_irq(void){ + read_reg(0x8, 0x8); + + //return irq status + bool irq = _tda18272hnm_regs.irq_status == tda18272hnm_regs_t::IRQ_STATUS_SET; + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): IRQ %d" + ) % (get_subdev_name()) % irq << std::endl; + + return irq; + } + + /*! + * In Power-On Reset State? + * Check POR logic for reset state (causes POR to clear) + * \return true for reset + */ + bool get_power_reset(void){ + read_reg(0x5, 0x5); + + //return POR state + bool por = _tda18272hnm_regs.por == tda18272hnm_regs_t::POR_RESET; + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): POR %d" + ) % (get_subdev_name()) % int(_tda18272hnm_regs.por) << std::endl; + + return por; + } + + /*! + * Get the lock detect status of the LO. + * \return sensor for locked + */ + sensor_value_t get_locked(void){ + read_reg(0x5, 0x5); + + //return lock detect + bool locked = _tda18272hnm_regs.lo_lock == tda18272hnm_regs_t::LO_LOCK_LOCKED; + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): locked %d" + ) % (get_subdev_name()) % locked << std::endl; + + return sensor_value_t("LO", locked, "locked", "unlocked"); + } + + /*! + * Read the RSSI from the registers + * Read the RSSI from the aux adc + * \return the rssi sensor in dB(m?) FIXME + */ + sensor_value_t get_rssi(void){ + //Launch RSSI calculation with MSM statemachine + _tda18272hnm_regs.set_reg(0x19, 0x80); //set MSM_byte_1 for rssi calculation + _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching rssi calculation + + send_reg(0x19, 0x1A); + + wait_irq(); + + //read rssi in dBuV + read_reg(0x7, 0x7); + + //calculate the rssi from the voltage + double rssi_dBuV = 40.0 + double(((110.0 - 40.0)/128.0) * _tda18272hnm_regs.get_reg(0x7)); + double rssi = rssi_dBuV - 107.0; //convert to dBm in 50ohm environment ( -108.8 if 75ohm ) FIXME + + return sensor_value_t("RSSI", rssi, "dBm"); + } + + /*! + * Read the Temperature from the registers + * \return the temp in degC + */ + sensor_value_t get_temp(void){ + //Enable Temperature reading + _tda18272hnm_regs.tm_on = tda18272hnm_regs_t::TM_ON_SENSOR_ON; + send_reg(0x4, 0x4); + + //read temp in degC + read_reg(0x3, 0x3); + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): Temperature %f C" + ) % (get_subdev_name()) % (double(_tda18272hnm_regs.tm_d)) << std::endl; + + //Disable Temperature reading + _tda18272hnm_regs.tm_on = tda18272hnm_regs_t::TM_ON_SENSOR_OFF; + send_reg(0x4, 0x4); + + return sensor_value_t("TEMP", double(_tda18272hnm_regs.tm_d), "degC"); + } +}; + +/*********************************************************************** + * Register the TVRX2 dboard + **********************************************************************/ +static dboard_base::sptr make_tvrx2(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new tvrx2(args)); +} + +UHD_STATIC_BLOCK(reg_tvrx2_dboard){ + //register the factory function for the rx dbid + dboard_manager::register_dboard(0x0046, &make_tvrx2, "TVRX2", tvrx2_sd_name_to_conn.keys()); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ + //FIXME for USRP1, we can only support one TVRX2 installed + + _rfcal_results = map_list_of + ( 0, tvrx2_tda18272_rfcal_result_t() ) + ( 1, tvrx2_tda18272_rfcal_result_t() ) + ( 2, tvrx2_tda18272_rfcal_result_t() ) + ( 3, tvrx2_tda18272_rfcal_result_t() ) + ( 4, tvrx2_tda18272_rfcal_result_t() ) + ( 5, tvrx2_tda18272_rfcal_result_t() ) + ( 6, tvrx2_tda18272_rfcal_result_t() ) + ( 7, tvrx2_tda18272_rfcal_result_t() ) + ( 8, tvrx2_tda18272_rfcal_result_t() ) + ( 9, tvrx2_tda18272_rfcal_result_t() ) + ( 10, tvrx2_tda18272_rfcal_result_t() ) + ( 11, tvrx2_tda18272_rfcal_result_t() ) + ; + + _rfcal_coeffs = map_list_of + ( 0, tvrx2_tda18272_rfcal_coeffs_t(0) ) + ( 1, tvrx2_tda18272_rfcal_coeffs_t(1) ) + ( 2, tvrx2_tda18272_rfcal_coeffs_t(3) ) + ( 3, tvrx2_tda18272_rfcal_coeffs_t(4) ) + ( 4, tvrx2_tda18272_rfcal_coeffs_t(6) ) + ( 5, tvrx2_tda18272_rfcal_coeffs_t(7) ) + ( 6, tvrx2_tda18272_rfcal_coeffs_t(9) ) + ( 7, tvrx2_tda18272_rfcal_coeffs_t(10) ) + ; + + //set defaults for LO, gains, and filter bandwidth + _bandwidth = 10e6; + + _if_freq = 12.5e6; + + _enabled = false; + + //send initial register settings + //this->read_reg(0x0, 0x43); + //this->send_reg(0x0, 0x43); + + //send magic xtal_cal_dac setting + send_reg(0x65, 0x65); + + //////////////////////////////////////////////////////////////////// + // Register properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name") + .set(get_rx_id().to_pp_string()); + this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&tvrx2::get_locked, this)); + this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi") + .publish(boost::bind(&tvrx2::get_rssi, this)); + this->get_rx_subtree()->create<sensor_value_t>("sensors/temperature") + .publish(boost::bind(&tvrx2::get_temp, this)); + BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){ + this->get_rx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&tvrx2::set_gain, this, _1, name)); + this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(tvrx2_gain_ranges[name]); + } + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&tvrx2::set_lo_freq, this, _1)); + this->get_rx_subtree()->create<meta_range_t>("freq/range") + .set(tvrx2_freq_range); + this->get_rx_subtree()->create<std::string>("antenna/value") + .set(tvrx2_sd_name_to_antennas[get_subdev_name()]); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(list_of(tvrx2_sd_name_to_antennas[get_subdev_name()])); + this->get_rx_subtree()->create<std::string>("connection") + .set(tvrx2_sd_name_to_conn[get_subdev_name()]); + this->get_rx_subtree()->create<bool>("enabled") + .coerce(boost::bind(&tvrx2::set_enabled, this, _1)) + .set(_enabled); + this->get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + this->get_rx_subtree()->create<double>("bandwidth/value") + .coerce(boost::bind(&tvrx2::set_bandwidth, this, _1)) + .set(_bandwidth); + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(tvrx2_bandwidth_range); + + //set the gpio directions and atr controls (identically) + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0); // All unused in atr + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, OUTPUT_MASK); // Set outputs + + //configure ref_clock + double ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); + + if (ref_clock == 64.0e6) { + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV4); + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): Dividing Refclock by 4" + ) % (get_subdev_name()) << std::endl; + + _freq_scalar = (4*16.0e6)/(this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)); + } else if (ref_clock == 100e6) { + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV6); + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): Dividing Refclock by 6" + ) % (get_subdev_name()) << std::endl; + + _freq_scalar = (6*16.0e6)/this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); + } else { + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV6); + UHD_MSG(warning) << boost::format("Unsupported ref_clock %0.2f, valid options 64e6 and 100e6") % ref_clock << std::endl; + _freq_scalar = 1.0; + } + + //enable only the clocks we need + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): Refclock %f Hz, scalar = %f" + ) % (get_subdev_name()) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % _freq_scalar << std::endl; + + _tda18272hnm_regs.irq_polarity = tda18272hnm_regs_t::IRQ_POLARITY_RAISED_VCC; + _tda18272hnm_regs.irq_clear = tda18272hnm_regs_t::IRQ_CLEAR_TRUE; + send_reg(0x37, 0x37); + send_reg(0xA, 0xA); + + send_reg(0x31, 0x31); //N_CP_Current + send_reg(0x36, 0x36); //RSSI_Clock + send_reg(0x24, 0x25); //AGC1_Do_step + send_reg(0x2C, 0x2C); //AGC1_Do_step + send_reg(0x2E, 0x2E); //AGC2_Do_step + send_reg(0x0E, 0x0E); //AGCs_Up_step_assym + send_reg(0x11, 0x11); //AGCs_Do_step_assym + + //intialize i2c + //soft_calibration(); + //tvrx2_tda18272_init_rfcal(); + transition_0(); +} + +bool tvrx2::set_enabled(bool enable){ + if (enable == _enabled) return _enabled; + + if (enable and not _enabled){ + //setup tuner parameters + transition_1(); + + transition_2(int(tvrx2_freq_range.start())); + + test_rf_filter_robustness(); + + BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){ + this->get_rx_subtree()->access<double>("gains/"+name+"/value") + .set(tvrx2_gain_ranges[name].start()); + } + + this->get_rx_subtree()->access<double>("bandwidth/value") + .set(_bandwidth); // default bandwidth from datasheet + + //transition_2 equivalent + this->get_rx_subtree()->access<double>("freq/value") + .set(tvrx2_freq_range.start()); + + //enter standby mode + transition_3(); + _enabled = true; + + } else { + //enter standby mode + transition_3(); + _enabled = false; + } + + return _enabled; +} + +tvrx2::~tvrx2(void){ + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): Called Destructor" + ) % (get_subdev_name()) << std::endl; + UHD_SAFE_CALL(if (_enabled) set_enabled(false);) +} + + +/*********************************************************************** + * TDA18272 Register IO Functions + **********************************************************************/ +void tvrx2::set_scaled_rf_freq(double rf_freq){ + _tda18272hnm_regs.set_rf_freq(_freq_scalar*rf_freq/1e3); +} + +double tvrx2::get_scaled_rf_freq(void){ + return _tda18272hnm_regs.get_rf_freq()*1e3/_freq_scalar; +} + +void tvrx2::set_scaled_if_freq(double if_freq){ + _tda18272hnm_regs.if_freq = int(_freq_scalar*if_freq/(50e3)); //max 12.8MHz?? +} + +double tvrx2::get_scaled_if_freq(void){ + return _tda18272hnm_regs.if_freq*50e3/_freq_scalar; +} + +void tvrx2::send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ + start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x43)); + stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x43)); + + for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t) - 1){ + int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) - 1 ? sizeof(boost::uint32_t) - 1 : stop_reg - start_addr + 1; + + //create buffer for register data (+1 for start address) + byte_vector_t regs_vector(num_bytes + 1); + + //first byte is the address of first register + regs_vector[0] = start_addr; + + //get the register data + for(int i=0; i<num_bytes; i++){ + regs_vector[1+i] = _tda18272hnm_regs.get_reg(start_addr+i); + UHD_LOGV(often) << boost::format( + "TVRX2 (%s, 0x%02x): send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" + ) % (get_subdev_name()) % int(tvrx2_sd_name_to_i2c_addr[get_subdev_name()]) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl; + } + + //send the data + this->get_iface()->write_i2c( + tvrx2_sd_name_to_i2c_addr[get_subdev_name()], regs_vector + ); + } +} + +void tvrx2::read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ + static const boost::uint8_t status_addr = 0x0; + start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x43)); + stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x43)); + + for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t)){ + int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) ? sizeof(boost::uint32_t) : stop_reg - start_addr + 1; + + //create buffer for starting address + byte_vector_t start_address_vector(1); + + //first byte is the address of first register + start_address_vector[0] = start_addr; + + //send the start address + this->get_iface()->write_i2c( + tvrx2_sd_name_to_i2c_addr[get_subdev_name()], start_address_vector + ); + + //create buffer for register data + byte_vector_t regs_vector(num_bytes); + + //read from i2c + regs_vector = this->get_iface()->read_i2c( + tvrx2_sd_name_to_i2c_addr[get_subdev_name()], num_bytes + ); + + for(boost::uint8_t i=0; i < num_bytes; i++){ + if (i + start_addr >= status_addr){ + _tda18272hnm_regs.set_reg(i + start_addr, regs_vector[i]); + } + UHD_LOGV(often) << boost::format( + "TVRX2 (%s, 0x%02x): read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" + ) % (get_subdev_name()) % int(tvrx2_sd_name_to_i2c_addr[get_subdev_name()]) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl; + } + } +} + + +/*********************************************************************** + * TDA18272 Calibration Functions + **********************************************************************/ +freq_range_t tvrx2::get_tda18272_rfcal_result_freq_range(boost::uint32_t result) +{ + + uhd::dict<boost::uint32_t, freq_range_t> result_to_cal_freq_ranges_map = map_list_of + ( 0, freq_range_t( + (double) tvrx2_tda18272_cal_map[0].cal_freq[_tda18272hnm_regs.rfcal_freq0] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[1].cal_freq[_tda18272hnm_regs.rfcal_freq1] * _freq_scalar + ) ) + ( 1, freq_range_t( + (double) tvrx2_tda18272_cal_map[1].cal_freq[_tda18272hnm_regs.rfcal_freq1] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[2].cal_freq[_tda18272hnm_regs.rfcal_freq2] * _freq_scalar + ) ) + ( 2, freq_range_t( + (double) tvrx2_tda18272_cal_map[2].cal_freq[_tda18272hnm_regs.rfcal_freq2] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[3].cal_freq[_tda18272hnm_regs.rfcal_freq3] * _freq_scalar + ) ) + ( 3, freq_range_t( + (double) tvrx2_tda18272_cal_map[3].cal_freq[_tda18272hnm_regs.rfcal_freq3] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[4].cal_freq[_tda18272hnm_regs.rfcal_freq4] * _freq_scalar + ) ) + ( 4, freq_range_t( + (double) tvrx2_tda18272_cal_map[4].cal_freq[_tda18272hnm_regs.rfcal_freq4] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[5].cal_freq[_tda18272hnm_regs.rfcal_freq5] * _freq_scalar + ) ) + ( 5, freq_range_t( + (double) tvrx2_tda18272_cal_map[5].cal_freq[_tda18272hnm_regs.rfcal_freq5] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[6].cal_freq[_tda18272hnm_regs.rfcal_freq6] * _freq_scalar + ) ) + ( 6, freq_range_t( + (double) tvrx2_tda18272_cal_map[6].cal_freq[_tda18272hnm_regs.rfcal_freq6] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[7].cal_freq[_tda18272hnm_regs.rfcal_freq7] * _freq_scalar + ) ) + ( 7, freq_range_t( + (double) tvrx2_tda18272_cal_map[7].cal_freq[_tda18272hnm_regs.rfcal_freq7] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[8].cal_freq[_tda18272hnm_regs.rfcal_freq8] * _freq_scalar + ) ) + ( 8, freq_range_t( + (double) tvrx2_tda18272_cal_map[8].cal_freq[_tda18272hnm_regs.rfcal_freq8] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[9].cal_freq[_tda18272hnm_regs.rfcal_freq9] * _freq_scalar + ) ) + ( 9, freq_range_t( + (double) tvrx2_tda18272_cal_map[9].cal_freq[_tda18272hnm_regs.rfcal_freq9] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[10].cal_freq[_tda18272hnm_regs.rfcal_freq10] * _freq_scalar + ) ) + (10, freq_range_t( + (double) tvrx2_tda18272_cal_map[10].cal_freq[_tda18272hnm_regs.rfcal_freq10] * _freq_scalar, + (double) tvrx2_tda18272_cal_map[11].cal_freq[_tda18272hnm_regs.rfcal_freq11] * _freq_scalar + ) ) + ; + + if (result < 11) + return result_to_cal_freq_ranges_map[result]; + else + return freq_range_t(0.0, 0.0); +} + + +/* + * Initialize the RF Filter calibration maps after hardware init + */ +void tvrx2::tvrx2_tda18272_init_rfcal(void) +{ + + /* read byte 0x38-0x43 */ + read_reg(0x38, 0x43); + + uhd::dict<boost::uint32_t, boost::uint8_t> result_to_cal_regs = map_list_of + ( 0, _tda18272hnm_regs.rfcal_log_1) + ( 1, _tda18272hnm_regs.rfcal_log_2) + ( 2, _tda18272hnm_regs.rfcal_log_3) + ( 3, _tda18272hnm_regs.rfcal_log_4) + ( 4, _tda18272hnm_regs.rfcal_log_5) + ( 5, _tda18272hnm_regs.rfcal_log_6) + ( 6, _tda18272hnm_regs.rfcal_log_7) + ( 7, _tda18272hnm_regs.rfcal_log_8) + ( 8, _tda18272hnm_regs.rfcal_log_9) + ( 9, _tda18272hnm_regs.rfcal_log_10) + (10, _tda18272hnm_regs.rfcal_log_11) + (11, _tda18272hnm_regs.rfcal_log_12) + ; + + + // Loop through rfcal_log_* registers, initialize _rfcal_results + BOOST_FOREACH(const boost::uint32_t &result, result_to_cal_regs.keys()) + _rfcal_results[result].delta_c = result_to_cal_regs[result] > 63 ? result_to_cal_regs[result] - 128 : result_to_cal_regs[result]; + + /* read byte 0x26-0x2B */ + read_reg(0x26, 0x2B); + + // Loop through rfcal_byte_* registers, initialize _rfcal_coeffs + BOOST_FOREACH(const boost::uint32_t &subband, _rfcal_coeffs.keys()) + { + freq_range_t subband_freqs; + + boost::uint32_t result = _rfcal_coeffs[subband].cal_number; + + subband_freqs = get_tda18272_rfcal_result_freq_range(result); + + _rfcal_coeffs[subband].RF_B1 = _rfcal_results[result].delta_c + tvrx2_tda18272_cal_map[result].c_offset[_rfcal_results[result].c_offset]; + + boost::uint32_t quotient = (((_rfcal_results[result+1].delta_c + tvrx2_tda18272_cal_map[result+1].c_offset[_rfcal_results[result].c_offset]) + - (_rfcal_results[result].delta_c + tvrx2_tda18272_cal_map[result].c_offset[_rfcal_results[result].c_offset])) * 1000000); + + boost::uint32_t divisor = ((boost::int32_t)(subband_freqs.stop() - subband_freqs.start())/1000); + + _rfcal_coeffs[subband].RF_A1 = quotient / divisor; + + } + +} + +/* + * Apply calibration coefficients to RF Filter tuning + */ +void tvrx2::tvrx2_tda18272_tune_rf_filter(boost::uint32_t uRF) +{ + boost::uint32_t uCounter = 0; + boost::uint8_t cal_result = 0; + boost::uint32_t uRFCal0 = 0; + boost::uint32_t uRFCal1 = 0; + boost::uint8_t subband = 0; + boost::int32_t cProg = 0; + boost::uint8_t gain_taper = 0; + boost::uint8_t RFBand = 0; + boost::int32_t RF_A1 = 0; + boost::int32_t RF_B1 = 0; + freq_range_t subband_freqs; + + /* read byte 0x26-0x2B */ + read_reg(0x26, 0x2B); + + subband_freqs = get_tda18272_rfcal_result_freq_range(1); + uRFCal0 = subband_freqs.start(); + subband_freqs = get_tda18272_rfcal_result_freq_range(4); + uRFCal1 = subband_freqs.start(); + + if(uRF < uRFCal0) + subband = 0; + else if(uRF < 145700000) + subband = 1; + else if(uRF < uRFCal1) + subband = 2; + else if(uRF < 367400000) + subband = 3; + else + { + subband_freqs = get_tda18272_rfcal_result_freq_range(7); + uRFCal0 = subband_freqs.start(); + subband_freqs = get_tda18272_rfcal_result_freq_range(10); + uRFCal1 = subband_freqs.start(); + + if(uRF < uRFCal0) + subband = 4; + else if(uRF < 625000000) + subband = 5; + else if(uRF < uRFCal1) + subband = 6; + else + subband = 7; + } + + cal_result = _rfcal_coeffs[subband].cal_number; + subband_freqs = get_tda18272_rfcal_result_freq_range(cal_result); + uRFCal0 = subband_freqs.start(); + + RF_A1 = _rfcal_coeffs[subband].RF_A1; + RF_B1 = _rfcal_coeffs[subband].RF_B1; + + uCounter = 0; + do uCounter ++; + while (uRF >= tvrx2_tda18272_freq_map[uCounter].rf_max && uCounter < TVRX2_TDA18272_FREQ_MAP_ENTRIES); + + cProg = tvrx2_tda18272_freq_map[uCounter - 1].c_prog; + gain_taper = tvrx2_tda18272_freq_map[uCounter - 1].gain_taper; + RFBand = tvrx2_tda18272_freq_map[uCounter - 1].rf_band; + + cProg = (boost::int32_t)(cProg + RF_B1 + (RF_A1*((boost::int32_t)(uRF - uRFCal0)/1000))/1000000); + + if(cProg>255) cProg = 255; + if(cProg<0) cProg = 0; + + _tda18272hnm_regs.rf_filter_bypass = 1; + _tda18272hnm_regs.rf_filter_cap = (boost::uint8_t) cProg; + _tda18272hnm_regs.gain_taper = gain_taper; + _tda18272hnm_regs.rf_filter_band = RFBand; + + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Software Calibration:\n" + "\tRF Filter Bypass = %d\n" + "\tRF Filter Cap = %d\n" + "\tRF Filter Band = %d\n" + "\tGain Taper = %d\n") + % (get_subdev_name()) + % int(_tda18272hnm_regs.rf_filter_bypass) + % int(_tda18272hnm_regs.rf_filter_cap) + % int(_tda18272hnm_regs.rf_filter_band) + % int(_tda18272hnm_regs.gain_taper) + << std::endl; + + send_reg(0x2c, 0x2f); +} + +void tvrx2::soft_calibration(void){ + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Software Calibration: Initialize Tuner, Calibrate and Standby\n") % (get_subdev_name()) << std::endl; + + _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL; + _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON; + _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON; + + send_reg(0x6, 0x6); + read_reg(0x6, 0x6); + + read_reg(0x19, 0x1A); + read_reg(0x26, 0x2B); + + _tda18272hnm_regs.rfcal_freq0 = 0x2; + _tda18272hnm_regs.rfcal_freq1 = 0x2; + _tda18272hnm_regs.rfcal_freq2 = 0x2; + _tda18272hnm_regs.rfcal_freq3 = 0x2; + _tda18272hnm_regs.rfcal_freq4 = 0x2; + _tda18272hnm_regs.rfcal_freq5 = 0x2; + _tda18272hnm_regs.rfcal_freq6 = 0x2; + _tda18272hnm_regs.rfcal_freq7 = 0x2; + _tda18272hnm_regs.rfcal_freq8 = 0x2; + _tda18272hnm_regs.rfcal_freq9 = 0x2; + _tda18272hnm_regs.rfcal_freq10 = 0x2; + _tda18272hnm_regs.rfcal_freq11 = 0x2; + + send_reg(0x26, 0x2B); + + _tda18272hnm_regs.set_reg(0x19, 0x3B); //set MSM_byte_1 for calibration per datasheet + _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration + + send_reg(0x19, 0x1A); + + wait_irq(); + + send_reg(0x1D, 0x1D); //Fmax_LO + send_reg(0x0C, 0x0C); //LT_Enable + send_reg(0x1B, 0x1B); //PSM_AGC1 + send_reg(0x0C, 0x0C); //AGC1_6_15dB + + //set spread spectrum for clock + //FIXME: NXP turns clock spread on and off + // based on where clock spurs would be relative to RF frequency + // we should do this also + _tda18272hnm_regs.digital_clock = tda18272hnm_regs_t::DIGITAL_CLOCK_SPREAD_OFF; + if (get_subdev_name() == "RX1") + //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO; + _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ; + else + //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO; + _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ; + + send_reg(0x14, 0x14); + + _tda18272hnm_regs.set_reg(0x36, 0x0E); //sets clock mode + send_reg(0x36, 0x36); + + //go to standby mode + _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY; + send_reg(0x6, 0x6); +} + +void tvrx2::test_rf_filter_robustness(void){ + typedef uhd::dict<std::string, std::string> tvrx2_filter_ratings_t; + typedef uhd::dict<std::string, double> tvrx2_filter_margins_t; + + tvrx2_filter_margins_t _filter_margins; + tvrx2_filter_ratings_t _filter_ratings; + + read_reg(0x38, 0x43); + + uhd::dict<std::string, boost::uint8_t> filter_cal_regs = map_list_of + ("VHFLow_0", 0x38) + ("VHFLow_1", 0x3a) + ("VHFHigh_0", 0x3b) + ("VHFHigh_1", 0x3d) + ("UHFLow_0", 0x3e) + ("UHFLow_1", 0x40) + ("UHFHigh_0", 0x41) + ("UHFHigh_1", 0x43) + ; + + BOOST_FOREACH(const std::string &name, filter_cal_regs.keys()){ + boost::uint8_t cal_result = _tda18272hnm_regs.get_reg(filter_cal_regs[name]); + if (cal_result & 0x80) { + _filter_ratings.set(name, "E"); + _filter_margins.set(name, 0.0); + } + else { + double partial; + + if (name == "VHFLow_0") + partial = 100 * (45 - 39.8225 * (1 + (0.31 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / 45.0; + + else if (name == "VHFLow_1") + partial = 100 * (152.1828 * (1 + (1.53 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (144.896 - 6)) / (144.896 - 6); + + else if (name == "VHFHigh_0") + partial = 100 * ((144.896 + 6) - 135.4063 * (1 + (0.27 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (144.896 + 6); + + else if (name == "VHFHigh_1") + partial = 100 * (383.1455 * (1 + (0.91 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (367.104 - 8)) / (367.104 - 8); + + else if (name == "UHFLow_0") + partial = 100 * ((367.104 + 8) - 342.6224 * (1 + (0.21 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (367.104 + 8); + + else if (name == "UHFLow_1") + partial = 100 * (662.5595 * (1 + (0.33 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (624.128 - 2)) / (624.128 - 2); + + else if (name == "UHFHigh_0") + partial = 100 * ((624.128 + 2) - 508.2747 * (1 + (0.23 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (624.128 + 2); + + else if (name == "UHFHigh_1") + partial = 100 * (947.8913 * (1 + (0.3 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (866 - 14)) / (866 - 14); + + else + UHD_THROW_INVALID_CODE_PATH(); + + _filter_margins.set(name, 0.0024 * partial * partial * partial - 0.101 * partial * partial + 1.629 * partial + 1.8266); + _filter_ratings.set(name, _filter_margins[name] >= 0.0 ? "H" : "L"); + } + } + + std::stringstream robustness_message; + robustness_message << boost::format("TVRX2 (%s): RF Filter Robustness Results:") % (get_subdev_name()) << std::endl; + BOOST_FOREACH(const std::string &name, uhd::sorted(_filter_ratings.keys())){ + robustness_message << boost::format("\t%s:\tMargin = %0.2f,\tRobustness = %c") % name % (_filter_margins[name]) % (_filter_ratings[name]) << std::endl; + } + + UHD_LOGV(often) << robustness_message.str(); +} + +/*********************************************************************** + * TDA18272 State Functions + **********************************************************************/ +void tvrx2::transition_0(void){ + //Transition 0: Initialize Tuner and place in standby + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Transistion 0: Initialize Tuner, Calibrate and Standby\n") % (get_subdev_name()) << std::endl; + + //Check for Power-On Reset, if reset, initialze tuner + if (get_power_reset()) { + _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL; + _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON; + _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON; + + send_reg(0x6, 0x6); + read_reg(0x6, 0x6); + + read_reg(0x19, 0x1A); + + _tda18272hnm_regs.set_reg(0x19, 0x3B); //set MSM_byte_1 for calibration per datasheet + _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration + + send_reg(0x19, 0x1A); + + wait_irq(); + } + + //send magic xtal_cal_dac setting + send_reg(0x65, 0x65); + + send_reg(0x1D, 0x1D); //Fmax_LO + send_reg(0x0C, 0x0C); //LT_Enable + send_reg(0x1B, 0x1B); //PSM_AGC1 + send_reg(0x0C, 0x0C); //AGC1_6_15dB + + //set spread spectrum for clock + //FIXME: NXP turns clock spread on and off + // based on where clock spurs would be relative to RF frequency + // we should do this also + _tda18272hnm_regs.digital_clock = tda18272hnm_regs_t::DIGITAL_CLOCK_SPREAD_OFF; + if (get_subdev_name() == "RX1") + //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO; + _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ; + else + //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO; + _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ; + + send_reg(0x14, 0x14); + + _tda18272hnm_regs.set_reg(0x36, 0x0E); //sets clock mode + send_reg(0x36, 0x36); + + //go to standby mode + _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY; + send_reg(0x6, 0x6); +} + +void tvrx2::transition_1(void){ + //Transition 1: Select TV Standard + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Transistion 1: Select TV Standard\n") % (get_subdev_name()) << std::endl; + + //send magic xtal_cal_dac setting + send_reg(0x65, 0x65); + + //Choose IF Byte 1 Setting + //_tda18272hnm_regs.if_hp_fc = tda18272hnm_regs_t::IF_HP_FC_0_4MHZ; + //_tda18272hnm_regs.if_notch = tda18272hnm_regs_t::IF_NOTCH_OFF; + //_tda18272hnm_regs.lp_fc_offset = tda18272hnm_regs_t::LP_FC_OFFSET_0_PERCENT; + //_tda18272hnm_regs.lp_fc = tda18272hnm_regs_t::LP_FC_10_0MHZ; + //send_reg(0x13, 0x13); + + //Choose IR Mixer Byte 2 Setting + //_tda18272hnm_regs.hi_pass = tda18272hnm_regs_t::HI_PASS_DISABLE; + //_tda18272hnm_regs.dc_notch = tda18272hnm_regs_t::DC_NOTCH_OFF; + send_reg(0x23, 0x23); + + //Set AGC TOP Bytes + send_reg(0x0C, 0x13); + + //Set PSM Byt1 + send_reg(0x1B, 0x1B); + + //Choose IF Frequency, setting is 50KHz steps + set_scaled_if_freq(_if_freq); + send_reg(0x15, 0x15); +} + +void tvrx2::transition_2(int rf_freq){ + //Transition 2: Select RF Frequency after changing TV Standard + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Transistion 2: Select RF Frequency after changing TV Standard\n") % (get_subdev_name()) << std::endl; + + //send magic xtal_cal_dac setting + send_reg(0x65, 0x65); + + //Wake up from Standby + _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL; + _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON; + _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON; + + send_reg(0x6, 0x6); + + //Set Clock Mode + _tda18272hnm_regs.set_reg(0x36, 0x00); + send_reg(0x36, 0x36); + + //Set desired RF Frequency + set_scaled_rf_freq(rf_freq); + send_reg(0x16, 0x18); + + //Lock PLL and tune RF Filters + _tda18272hnm_regs.set_reg(0x19, 0x41); //set MSM_byte_1 for RF Filters Tuning, PLL Locking + _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration + + send_reg(0x19, 0x1A); + + wait_irq(); + + tvrx2_tda18272_tune_rf_filter(rf_freq); + + ////LO Lock state in Reg 0x5 LSB + //read_reg(0x6, 0x6); + +} + +void tvrx2::transition_3(void){ + //Transition 3: Standby Mode + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Transistion 3: Standby Mode\n") % (get_subdev_name()) << std::endl; + + //send magic xtal_cal_dac setting + send_reg(0x65, 0x65); + + //Set clock mode + _tda18272hnm_regs.set_reg(0x36, 0x0E); + send_reg(0x36, 0x36); + + //go to standby mode + _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY; + _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_OFF; + _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_OFF; + send_reg(0x6, 0x6); +} + +void tvrx2::transition_4(int rf_freq){ + //Transition 4: Change RF Frequency without changing TV Standard + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Transistion 4: Change RF Frequency without changing TV Standard\n") % (get_subdev_name()) << std::endl; + + //send magic xtal_cal_dac setting + send_reg(0x65, 0x65); + + //Set desired RF Frequency + set_scaled_rf_freq(rf_freq); + send_reg(0x16, 0x18); + + //Lock PLL and tune RF Filters + _tda18272hnm_regs.set_reg(0x19, 0x41); //set MSM_byte_1 for RF Filters Tuning, PLL Locking + _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration + + send_reg(0x19, 0x1A); + + wait_irq(); + + tvrx2_tda18272_tune_rf_filter(rf_freq); + + ////LO Lock state in Reg 0x5 LSB + //read_reg(0x5, 0x6); + +} + +void tvrx2::wait_irq(void){ + int timeout = 20; //irq waiting timeout in milliseconds + //int irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & int(tvrx2_sd_name_to_irq_io[get_subdev_name()])); + bool irq = get_irq(); + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Waiting on IRQ, subdev = %d, mask = 0x%x, Status: 0x%x\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq << std::endl; + + while (not irq and timeout > 0) { + //irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & tvrx2_sd_name_to_irq_io[get_subdev_name()]); + irq = get_irq(); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + timeout -= 1; + } + + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): IRQ Raised, subdev = %d, mask = 0x%x, Status: 0x%x, Timeout: %d\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq % timeout << std::endl; + + read_reg(0xA, 0xB); + //UHD_ASSERT_THROW(timeout > 0); + if(timeout <= 0) UHD_MSG(warning) << boost::format( + "\nTVRX2 (%s): Timeout waiting on IRQ\n") % (get_subdev_name()) << std::endl; + + _tda18272hnm_regs.irq_clear = tda18272hnm_regs_t::IRQ_CLEAR_TRUE; + send_reg(0xA, 0xA); + read_reg(0xA, 0xB); + + irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & tvrx2_sd_name_to_irq_io[get_subdev_name()]); + + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): Cleared IRQ, subdev = %d, mask = 0x%x, Status: 0x%x\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq << std::endl; +} + + + +/*********************************************************************** + * Tuning + **********************************************************************/ +double tvrx2::set_lo_freq(double target_freq){ + //target_freq = std::clip(target_freq, tvrx2_freq_range.min, tvrx2_freq_range.max); + + read_reg(0x6, 0x6); + + if (_tda18272hnm_regs.sm == tda18272hnm_regs_t::SM_STANDBY) { + transition_2(int(target_freq + _bandwidth/2 - get_scaled_if_freq())); + } else { + transition_4(int(target_freq + _bandwidth/2 - get_scaled_if_freq())); + } + read_reg(0x16, 0x18); + + //compute actual tuned frequency + _lo_freq = get_scaled_rf_freq() + get_scaled_if_freq(); // - _bandwidth/2; + + //debug output of calculated variables + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): LO Frequency\n" + "\tRequested: \t%f\n" + "\tComputed: \t%f\n" + "\tReadback: \t%f\n" + "\tIF Frequency: \t%f\n") % (get_subdev_name()) % target_freq % double(int(target_freq/1e3)*1e3) % get_scaled_rf_freq() % get_scaled_if_freq() << std::endl; + + get_locked(); + + test_rf_filter_robustness(); + + UHD_LOGV(often) << boost::format( + "\nTVRX2 (%s): RSSI = %f dBm\n" + ) % (get_subdev_name()) % (get_rssi().to_real()) << std::endl; + + return _lo_freq; +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/* + * Convert the requested gain into a dac voltage + */ +static double gain_to_if_gain_dac(double &gain){ + //clip the input + gain = tvrx2_gain_ranges["IF"].clip(gain); + + //voltage level constants + static const double max_volts = double(1.7), min_volts = double(0.5); + static const double slope = (max_volts-min_volts)/tvrx2_gain_ranges["IF"].stop(); + + //calculate the voltage for the aux dac + double dac_volts = gain*slope + min_volts; + + UHD_LOGV(often) << boost::format( + "TVRX2 IF Gain: %f dB, dac_volts: %f V" + ) % gain % dac_volts << std::endl; + + //the actual gain setting + gain = (dac_volts - min_volts)/slope; + + return dac_volts; +} + +double tvrx2::set_gain(double gain, const std::string &name){ + assert_has(tvrx2_gain_ranges.keys(), name, "tvrx2 gain name"); + + if (name == "IF"){ + //write voltage to aux_dac + this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, tvrx2_sd_name_to_dac[get_subdev_name()], gain_to_if_gain_dac(gain)); + } + else UHD_THROW_INVALID_CODE_PATH(); + + //shadow gain setting + _gains[name] = gain; + + return gain; +} + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +static tda18272hnm_regs_t::lp_fc_t bandwidth_to_lp_fc_reg(double &bandwidth){ + int reg = uhd::clip(boost::math::iround((bandwidth-5.0e6)/1.0e6), 0, 4); + + switch(reg){ + case 0: + bandwidth = 1.7e6; + return tda18272hnm_regs_t::LP_FC_1_7MHZ; + case 1: + bandwidth = 6e6; + return tda18272hnm_regs_t::LP_FC_6_0MHZ; + case 2: + bandwidth = 7e6; + return tda18272hnm_regs_t::LP_FC_7_0MHZ; + case 3: + bandwidth = 8e6; + return tda18272hnm_regs_t::LP_FC_8_0MHZ; + case 4: + bandwidth = 10e6; + return tda18272hnm_regs_t::LP_FC_10_0MHZ; + } + UHD_THROW_INVALID_CODE_PATH(); +} + +double tvrx2::set_bandwidth(double bandwidth){ + //clip the input + bandwidth = tvrx2_bandwidth_range.clip(bandwidth); + + //compute low pass cutoff frequency setting + _tda18272hnm_regs.lp_fc = bandwidth_to_lp_fc_reg(bandwidth); + + //shadow bandwidth setting + _bandwidth = bandwidth; + + //update register + send_reg(0x13, 0x13); + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s) Bandwidth (lp_fc): %f Hz, reg: %d" + ) % (get_subdev_name()) % _bandwidth % (int(_tda18272hnm_regs.lp_fc)) << std::endl; + + return _bandwidth; +} diff --git a/host/lib/usrp/dboard/db_unknown.cpp b/host/lib/usrp/dboard/db_unknown.cpp new file mode 100644 index 000000000..2ed50cd91 --- /dev/null +++ b/host/lib/usrp/dboard/db_unknown.cpp @@ -0,0 +1,160 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/ranges.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/tuple/tuple.hpp> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * Utility functions + **********************************************************************/ +static void warn_if_old_rfx(const dboard_id_t &dboard_id, const std::string &xx){ + typedef boost::tuple<std::string, dboard_id_t, dboard_id_t> old_ids_t; //name, rx_id, tx_id + static const std::vector<old_ids_t> old_rfx_ids = list_of + (old_ids_t("Flex 400 Classic", 0x0004, 0x0008)) + (old_ids_t("Flex 900 Classic", 0x0005, 0x0009)) + (old_ids_t("Flex 1200 Classic", 0x0006, 0x000a)) + (old_ids_t("Flex 1800 Classic", 0x0030, 0x0031)) + (old_ids_t("Flex 2400 Classic", 0x0007, 0x000b)) + ; + BOOST_FOREACH(const old_ids_t &old_id, old_rfx_ids){ + std::string name; dboard_id_t rx_id, tx_id; + boost::tie(name, rx_id, tx_id) = old_id; + if ( + (xx == "RX" and rx_id == dboard_id) or + (xx == "TX" and tx_id == dboard_id) + ) UHD_MSG(warning) << boost::format( + "Detected %s daughterboard %s\n" + "This board requires modification to use.\n" + "See the daughterboard application notes.\n" + ) % xx % name; + } +} + +/*********************************************************************** + * The unknown boards: + * Like a basic board, but with only one subdev. + **********************************************************************/ +class unknown_rx : public rx_dboard_base{ +public: + unknown_rx(ctor_args_t args); +}; + +class unknown_tx : public tx_dboard_base{ +public: + unknown_tx(ctor_args_t args); +}; + +/*********************************************************************** + * Register the unknown dboards + **********************************************************************/ +static dboard_base::sptr make_unknown_rx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new unknown_rx(args)); +} + +static dboard_base::sptr make_unknown_tx(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new unknown_tx(args)); +} + +UHD_STATIC_BLOCK(reg_unknown_dboards){ + dboard_manager::register_dboard(0xfff0, &make_unknown_tx, "Unknown TX"); + dboard_manager::register_dboard(0xfff1, &make_unknown_rx, "Unknown RX"); +} + +/*********************************************************************** + * Unknown RX dboard + **********************************************************************/ +unknown_rx::unknown_rx(ctor_args_t args) : rx_dboard_base(args){ + warn_if_old_rfx(this->get_rx_id(), "RX"); + + //////////////////////////////////////////////////////////////////// + // Register properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name").set( + std::string(str(boost::format("%s - %s") + % get_rx_id().to_pp_string() + % get_subdev_name() + ))); + this->get_rx_subtree()->create<int>("gains"); //phony property so this dir exists + this->get_rx_subtree()->create<double>("freq/value") + .set(double(0.0)); + this->get_rx_subtree()->create<meta_range_t>("freq/range") + .set(freq_range_t(double(0.0), double(0.0))); + this->get_rx_subtree()->create<std::string>("antenna/value") + .set(""); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(list_of("")); + this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists + this->get_rx_subtree()->create<std::string>("connection") + .set("IQ"); + this->get_rx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + this->get_rx_subtree()->create<double>("bandwidth/value") + .set(double(0.0)); + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(0.0, 0.0)); +} + +/*********************************************************************** + * Unknown TX dboard + **********************************************************************/ +unknown_tx::unknown_tx(ctor_args_t args) : tx_dboard_base(args){ + warn_if_old_rfx(this->get_tx_id(), "TX"); + + //////////////////////////////////////////////////////////////////// + // Register properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<std::string>("name").set( + std::string(str(boost::format("%s - %s") + % get_tx_id().to_pp_string() + % get_subdev_name() + ))); + this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists + this->get_tx_subtree()->create<double>("freq/value") + .set(double(0.0)); + this->get_tx_subtree()->create<meta_range_t>("freq/range") + .set(freq_range_t(double(0.0), double(0.0))); + this->get_tx_subtree()->create<std::string>("antenna/value") + .set(""); + this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(list_of("")); + this->get_tx_subtree()->create<int>("sensors"); //phony property so this dir exists + this->get_tx_subtree()->create<std::string>("connection") + .set("IQ"); + this->get_tx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_tx_subtree()->create<bool>("use_lo_offset") + .set(false); + this->get_tx_subtree()->create<double>("bandwidth/value") + .set(double(0.0)); + this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(0.0, 0.0)); +} diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp new file mode 100644 index 000000000..58ce03d79 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_common.cpp @@ -0,0 +1,152 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "db_wbx_common.hpp" +#include "adf4350_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * Gain-related functions + **********************************************************************/ +static int rx_pga0_gain_to_iobits(double &gain){ + //clip the input + gain = wbx_rx_gain_ranges["PGA0"].clip(gain); + + //convert to attenuation + double attn = wbx_rx_gain_ranges["PGA0"].stop() - gain; + + //calculate the attenuation + int attn_code = boost::math::iround(attn*2); + int iobits = ((~attn_code) << RX_ATTN_SHIFT) & RX_ATTN_MASK; + + UHD_LOGV(often) << boost::format( + "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 + gain = wbx_rx_gain_ranges["PGA0"].stop() - double(attn_code)/2; + + return iobits; +} + + +/*********************************************************************** + * WBX Common Implementation + **********************************************************************/ +wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ + + //enable the clocks that we need + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + + //////////////////////////////////////////////////////////////////// + // Register RX properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX)); + BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ + this->get_rx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&wbx_base::set_rx_gain, this, _1, name)) + .set(wbx_rx_gain_ranges[name].start()); + this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(wbx_rx_gain_ranges[name]); + } + this->get_rx_subtree()->create<std::string>("connection").set("IQ"); + this->get_rx_subtree()->create<bool>("enabled") + .subscribe(boost::bind(&wbx_base::set_rx_enabled, this, _1)) + .set(true); //start enabled + this->get_rx_subtree()->create<bool>("use_lo_offset").set(false); + this->get_rx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(2*20.0e6, 2*20.0e6)); + + //////////////////////////////////////////////////////////////////// + // Register TX properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX)); + this->get_tx_subtree()->create<std::string>("connection").set("IQ"); + this->get_tx_subtree()->create<bool>("use_lo_offset").set(false); + this->get_tx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(2*20.0e6, 2*20.0e6)); + + // instantiate subclass foo + switch(get_rx_id().to_uint16()) { + case 0x053: + db_actual = wbx_versionx_sptr(new wbx_version2(this)); + return; + case 0x057: + db_actual = wbx_versionx_sptr(new wbx_version3(this)); + return; + case 0x063: + db_actual = wbx_versionx_sptr(new wbx_version4(this)); + return; + default: + /* We didn't recognize the version of the board... */ + UHD_THROW_INVALID_CODE_PATH(); + } + +} + + +wbx_base::~wbx_base(void){ + /* NOP */ +} + +/*********************************************************************** + * Enables + **********************************************************************/ +void wbx_base::set_rx_enabled(bool enb){ + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, + (enb)? RX_POWER_UP : RX_POWER_DOWN, RX_POWER_UP | RX_POWER_DOWN + ); +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +double wbx_base::set_rx_gain(double gain, const std::string &name){ + assert_has(wbx_rx_gain_ranges.keys(), name, "wbx rx gain name"); + if(name == "PGA0"){ + boost::uint16_t io_bits = rx_pga0_gain_to_iobits(gain); + _rx_gains[name] = gain; + + //write the new gain to rx gpio outputs + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, io_bits, RX_ATTN_MASK); + } + else UHD_THROW_INVALID_CODE_PATH(); + return _rx_gains[name]; //returned shadowed +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +sensor_value_t wbx_base::get_locked(dboard_iface::unit_t unit){ + const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; + return sensor_value_t("LO", locked, "locked", "unlocked"); +} diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp new file mode 100644 index 000000000..9e984dce7 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_common.hpp @@ -0,0 +1,204 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP +#define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP + +// Common IO Pins +#define ADF4350_CE (1 << 3) +#define ADF4350_PDBRF (1 << 2) +#define ADF4350_MUXOUT (1 << 1) // INPUT!!! +#define ADF4351_CE (1 << 3) +#define ADF4351_PDBRF (1 << 2) +#define ADF4351_MUXOUT (1 << 1) // INPUT!!! + +#define LOCKDET_MASK (1 << 0) // INPUT!!! + +// TX IO Pins +#define TX_PUP_5V (1 << 7) // enables 5.0V power supply +#define TX_PUP_3V (1 << 6) // enables 3.3V supply +#define TXMOD_EN (1 << 4) // on UNIT_TX, 1 enables TX Modulator + +// RX IO Pins +#define RX_PUP_5V (1 << 7) // enables 5.0V power supply +#define RX_PUP_3V (1 << 6) // enables 3.3V supply +#define RXBB_PDB (1 << 4) // on UNIT_RX, 1 powers up RX baseband + +// RX Attenuator Pins +#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) // for v3, TXMOD_EN tied to ADF4350_PDBRF rather than separate +#define TX_MIXER_DIS 0 + +#define RX_MIXER_ENB (RXBB_PDB|ADF4350_PDBRF) +#define RX_MIXER_DIS 0 + +// Power functions +#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 +#define RX_POWER_DOWN 0 + + +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/bind.hpp> + +namespace uhd{ namespace usrp{ + + +/*********************************************************************** + * The WBX Common dboard constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_rx_gain_ranges = boost::assign::map_list_of + ("PGA0", gain_range_t(0, 31.5, 0.5)); + + +/*********************************************************************** + * The WBX dboard base class + **********************************************************************/ +class wbx_base : public xcvr_dboard_base{ +public: + wbx_base(ctor_args_t args); + virtual ~wbx_base(void); + +protected: + virtual double set_rx_gain(double gain, const std::string &name); + + virtual void set_rx_enabled(bool enb); + + /*! + * Get the lock detect status of the LO. + * + * This operation is identical for all versions of the WBX board. + * \param unit which unit rx or tx + * \return true for locked + */ + virtual sensor_value_t get_locked(dboard_iface::unit_t unit); + + /*! + * Version-agnostic ABC that wraps version-specific implementations of the + * WBX base daughterboard. + * + * This class is an abstract base class, and thus is impossible to + * instantiate. + */ + class wbx_versionx { + public: + wbx_versionx() {} + ~wbx_versionx(void) {} + + virtual double set_tx_gain(double gain, const std::string &name) = 0; + virtual void set_tx_enabled(bool enb) = 0; + virtual double set_lo_freq(dboard_iface::unit_t unit, double target_freq) = 0; + + /*! This is the registered instance of the wrapper class, wbx_base. */ + wbx_base *self_base; + + property_tree::sptr get_rx_subtree(void){ + return self_base->get_rx_subtree(); + } + + property_tree::sptr get_tx_subtree(void){ + return self_base->get_tx_subtree(); + } + }; + + + /*! + * Version 2 of the WBX Daughterboard + * + * Basically the original release of the DB. + */ + class wbx_version2 : public wbx_versionx { + public: + wbx_version2(wbx_base *_self_wbx_base); + ~wbx_version2(void); + + double set_tx_gain(double gain, const std::string &name); + void set_tx_enabled(bool enb); + double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + }; + + /*! + * Version 3 of the WBX Daughterboard + * + * Fixed a problem with the AGC from Version 2. + */ + class wbx_version3 : public wbx_versionx { + public: + wbx_version3(wbx_base *_self_wbx_base); + ~wbx_version3(void); + + double set_tx_gain(double gain, const std::string &name); + void set_tx_enabled(bool enb); + double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + }; + + /*! + * Version 4 of the WBX Daughterboard + * + * Upgrades the Frequnecy Synthensizer from ADF4350 to ADF4351. + */ + class wbx_version4 : public wbx_versionx { + public: + wbx_version4(wbx_base *_self_wbx_base); + ~wbx_version4(void); + + double set_tx_gain(double gain, const std::string &name); + void set_tx_enabled(bool enb); + double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + }; + + /*! + * Handle to the version-specific implementation of the WBX. + * + * Since many of this class's functions are dependent on the version of the + * WBX board, this class will instantiate an object of the appropriate + * wbx_version_* subclass, and invoke any relevant functions through that + * object. This pointer is set to the proper object at construction time. + */ + typedef boost::shared_ptr<wbx_versionx> wbx_versionx_sptr; + wbx_versionx_sptr db_actual; + + uhd::dict<std::string, double> _tx_gains, _rx_gains; + bool _rx_enabled, _tx_enabled; +}; + + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP */ diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp new file mode 100644 index 000000000..3d633a672 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_simple.cpp @@ -0,0 +1,159 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Antenna constants +#define ANTSW_IO ((1 << 15)) // on UNIT_TX, 0 = TX, 1 = RX, on UNIT_RX 0 = main ant, 1 = RX2 +#define ANT_TX 0 //the tx line is transmitting +#define ANT_RX ANTSW_IO //the tx line is receiving +#define ANT_TXRX 0 //the rx line is on txrx +#define ANT_RX2 ANTSW_IO //the rx line in on rx2 + +#include "db_wbx_common.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * The WBX Simple dboard constants + **********************************************************************/ +static const std::vector<std::string> wbx_tx_antennas = list_of("TX/RX")("CAL"); + +static const std::vector<std::string> wbx_rx_antennas = list_of("TX/RX")("RX2")("CAL"); + +/*********************************************************************** + * The WBX simple implementation + **********************************************************************/ +class wbx_simple : public wbx_base{ +public: + wbx_simple(ctor_args_t args); + ~wbx_simple(void); + +private: + void set_rx_ant(const std::string &ant); + void set_tx_ant(const std::string &ant); + std::string _rx_ant; +}; + +/*********************************************************************** + * Register the WBX simple implementation + **********************************************************************/ +static dboard_base::sptr make_wbx_simple(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new wbx_simple(args)); +} + +/*********************************************************************** + * ID Numbers for WBX daughterboard combinations. + **********************************************************************/ +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"); + dboard_manager::register_dboard(0x0063, 0x0062, &make_wbx_simple, "WBX v4"); + dboard_manager::register_dboard(0x0063, 0x004f, &make_wbx_simple, "WBX v4 + Simple GDB"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ + + //////////////////////////////////////////////////////////////////// + // Register RX properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->access<std::string>("name").set( + this->get_rx_subtree()->access<std::string>("name").get() + " + Simple GDB"); + this->get_rx_subtree()->create<std::string>("antenna/value") + .subscribe(boost::bind(&wbx_simple::set_rx_ant, this, _1)) + .set("RX2"); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(wbx_rx_antennas); + + //////////////////////////////////////////////////////////////////// + // Register TX properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->access<std::string>("name").set( + this->get_tx_subtree()->access<std::string>("name").get() + " + Simple GDB"); + this->get_tx_subtree()->create<std::string>("antenna/value") + .subscribe(boost::bind(&wbx_simple::set_tx_ant, this, _1)) + .set(wbx_tx_antennas.at(0)); + this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(wbx_tx_antennas); + + //set the gpio directions and atr controls (antenna switches all under ATR) + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, ANTSW_IO, ANTSW_IO); + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, ANTSW_IO, ANTSW_IO); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); + + //setup ATR for the antenna switches (constant) + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); +} + +wbx_simple::~wbx_simple(void){ + /* NOP */ +} + +/*********************************************************************** + * Antennas + **********************************************************************/ +void wbx_simple::set_rx_ant(const std::string &ant){ + //validate input + assert_has(wbx_rx_antennas, ant, "wbx rx antenna name"); + + //shadow the setting + _rx_ant = ant; + + //write the new antenna setting to atr regs + if (_rx_ant == "CAL") { + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO); + } + else { + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO); + } +} + +void wbx_simple::set_tx_ant(const std::string &ant){ + assert_has(wbx_tx_antennas, ant, "wbx tx antenna name"); + + //write the new antenna setting to atr regs + if (ant == "CAL") { + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO); + } + else { + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + } +} diff --git a/host/lib/usrp/dboard/db_wbx_version2.cpp b/host/lib/usrp/dboard/db_wbx_version2.cpp new file mode 100644 index 000000000..ad31339e7 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_version2.cpp @@ -0,0 +1,329 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "db_wbx_common.hpp" +#include "adf4350_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * WBX Version 2 Constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_v2_tx_gain_ranges = map_list_of + ("PGA0", gain_range_t(0, 25, 0.05)) +; + +static const freq_range_t wbx_v2_freq_range(68.75e6, 2.2e9); + +/*********************************************************************** + * Gain-related functions + **********************************************************************/ +static double tx_pga0_gain_to_dac_volts(double &gain){ + //clip the input + gain = wbx_v2_tx_gain_ranges["PGA0"].clip(gain); + + //voltage level constants + static const double max_volts = 0.5, min_volts = 1.4; + static const double slope = (max_volts-min_volts)/wbx_v2_tx_gain_ranges["PGA0"].stop(); + + //calculate the voltage for the aux dac + double dac_volts = gain*slope + min_volts; + + UHD_LOGV(often) << boost::format( + "WBX TX Gain: %f dB, dac_volts: %f V" + ) % gain % dac_volts << std::endl; + + //the actual gain setting + gain = (dac_volts - min_volts)/slope; + + return dac_volts; +} + + +/*********************************************************************** + * WBX Version 2 Implementation + **********************************************************************/ +wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { + //register our handle on the primary wbx_base instance + self_base = _self_wbx_base; + + //////////////////////////////////////////////////////////////////// + // Register RX properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name").set("WBX RX v2"); + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0); + this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range); + + //////////////////////////////////////////////////////////////////// + // Register TX properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<std::string>("name").set("WBX TX v2"); + BOOST_FOREACH(const std::string &name, wbx_v2_tx_gain_ranges.keys()){ + self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name)) + .set(wbx_v2_tx_gain_ranges[name].start()); + self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(wbx_v2_tx_gain_ranges[name]); + } + this->get_tx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0); + this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range); + this->get_tx_subtree()->create<bool>("enabled") + .subscribe(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1)) + .set(true); //start enabled + + //set attenuator control bits + int v2_iobits = ADF4350_CE; + int v2_tx_mod = TXMOD_EN|ADF4350_PDBRF; + + //set the gpio directions and atr controls + self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v2_tx_mod); + self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4350_PDBRF); + self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v2_tx_mod|v2_iobits); + self_base->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) + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); +} + +wbx_base::wbx_version2::~wbx_version2(void){ + /* NOP */ +} + +/*********************************************************************** + * Enables + **********************************************************************/ +void wbx_base::wbx_version2::set_tx_enabled(bool enb){ + self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, + (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | ADF4350_CE); +} + + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +double wbx_base::wbx_version2::set_tx_gain(double gain, const std::string &name){ + assert_has(wbx_v2_tx_gain_ranges.keys(), name, "wbx tx gain name"); + if(name == "PGA0"){ + double dac_volts = tx_pga0_gain_to_dac_volts(gain); + self_base->_tx_gains[name] = gain; + + //write the new voltage to the aux dac + self_base->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts); + } + else UHD_THROW_INVALID_CODE_PATH(); + return self_base->_tx_gains[name]; //shadowed +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { + //clip to tuning range + target_freq = wbx_v2_freq_range.clip(target_freq); + + UHD_LOGV(often) << boost::format( + "WBX tune: target frequency %f Mhz" + ) % (target_freq/1e6) << std::endl; + + //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) + static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of + (0,23) //adf4350_regs_t::PRESCALER_4_5 + (1,75) //adf4350_regs_t::PRESCALER_8_9 + ; + + //map rf divider select output dividers to enums + static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of + (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) + (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) + (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) + (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) + (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) + ; + + double actual_freq, pfd_freq; + double ref_freq = self_base->get_iface()->get_clock_rate(unit); + int R=0, BS=0, N=0, FRAC=0, MOD=0; + int RFdiv = 1; + adf4350_regs_t::reference_divide_by_2_t T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + adf4350_regs_t::reference_doubler_t D = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; + + //Reference doubler for 50% duty cycle + // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 + if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; + + //increase RF divider until acceptable VCO frequency + //start with target_freq*2 because mixer has divide by 2 + double vco_freq = target_freq*2; + while (vco_freq < 2.2e9) { + vco_freq *= 2; + RFdiv *= 2; + } + + //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exists when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_rf = f_vco/RFdiv) + * f_actual = f_rf/2 + */ + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = ref_freq*(1+D)/(R*(1+T)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > 25e6) continue; + + //ignore fractional part of tuning + N = int(std::floor(vco_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < prescaler_to_min_int_div[prescaler]) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below 100KHz + //constraint on band select clock + if (pfd_freq/BS > 100e3) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = int((vco_freq/pfd_freq - N)*MOD); + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0){ + T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; + R /= 2; + } + + //actual frequency calculation + actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); + + + UHD_LOGV(often) + << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + + << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%s" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv % self_base->get_locked(unit).to_pp_string() << std::endl + << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" + ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + + //load the register values + adf4350_regs_t regs; + + regs.frac_12_bit = FRAC; + regs.int_16_bit = N; + regs.mod_12_bit = MOD; + regs.prescaler = prescaler; + regs.r_counter_10_bit = R; + regs.reference_divide_by_2 = T; + regs.reference_doubler = D; + regs.band_select_clock_div = BS; + UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); + regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + + if (unit == dboard_iface::UNIT_RX) { + freq_range_t rx_lo_5dbm = list_of + (range_t(0.05e9, 1.4e9)) + ; + + freq_range_t rx_lo_2dbm = list_of + (range_t(1.4e9, 2.2e9)) + ; + + if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + + if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; + + } else if (unit == dboard_iface::UNIT_TX) { + freq_range_t tx_lo_5dbm = list_of + (range_t(0.05e9, 1.7e9)) + (range_t(1.9e9, 2.2e9)) + ; + + freq_range_t tx_lo_m1dbm = list_of + (range_t(1.7e9, 1.9e9)) + ; + + if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + + if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM; + + } + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + int addr; + + for(addr=5; addr>=0; addr--){ + UHD_LOGV(often) << boost::format( + "WBX SPI Reg (0x%02x): 0x%08x" + ) % addr % regs.get_reg(addr) << std::endl; + self_base->get_iface()->write_spi( + unit, spi_config_t::EDGE_RISE, + regs.get_reg(addr), 32 + ); + } + + //return the actual frequency + UHD_LOGV(often) << boost::format( + "WBX tune: actual frequency %f Mhz" + ) % (actual_freq/1e6) << std::endl; + return actual_freq; +} diff --git a/host/lib/usrp/dboard/db_wbx_version3.cpp b/host/lib/usrp/dboard/db_wbx_version3.cpp new file mode 100644 index 000000000..7ef47edd4 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_version3.cpp @@ -0,0 +1,361 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "db_wbx_common.hpp" +#include "adf4350_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * WBX Version 3 Constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_v3_tx_gain_ranges = map_list_of + ("PGA0", gain_range_t(0, 31, 1.0)) +; + +static const freq_range_t wbx_v3_freq_range(68.75e6, 2.2e9); + +/*********************************************************************** + * Gain-related functions + **********************************************************************/ +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); + 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); + + return iobits; +} + + +/*********************************************************************** + * WBX Common Implementation + **********************************************************************/ +wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { + //register our handle on the primary wbx_base instance + self_base = _self_wbx_base; + + //////////////////////////////////////////////////////////////////// + // Register RX properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name").set("WBX RX v3"); + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0); + this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range); + + //////////////////////////////////////////////////////////////////// + // Register TX properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<std::string>("name").set("WBX TX v3"); + BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){ + self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name)) + .set(wbx_v3_tx_gain_ranges[name].start()); + self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(wbx_v3_tx_gain_ranges[name]); + } + this->get_tx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0); + this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range); + this->get_tx_subtree()->create<bool>("enabled") + .subscribe(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1)) + .set(true); //start enabled + + //set attenuator control bits + int v3_iobits = TX_ATTN_MASK; + int v3_tx_mod = ADF4350_PDBRF; + + //set the gpio directions and atr controls + self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, \ + v3_tx_mod|v3_iobits); + self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, \ + RXBB_PDB|ADF4350_PDBRF); + self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, \ + TX_PUP_5V|TX_PUP_3V|v3_tx_mod|v3_iobits); + self_base->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). set TX gain iobits to min gain (max attenuation) + //when RX_ONLY or IDLE to suppress LO leakage + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ + dboard_iface::ATR_REG_IDLE, v3_tx_mod, \ + TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ + dboard_iface::ATR_REG_RX_ONLY, v3_tx_mod, \ + TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ + dboard_iface::ATR_REG_TX_ONLY, v3_tx_mod, \ + TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ + dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, \ + TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); + + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ + dboard_iface::ATR_REG_IDLE, \ + RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ + dboard_iface::ATR_REG_TX_ONLY, \ + RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ + dboard_iface::ATR_REG_RX_ONLY, \ + RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ + dboard_iface::ATR_REG_FULL_DUPLEX, \ + RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); +} + +wbx_base::wbx_version3::~wbx_version3(void){ + /* NOP */ +} + + +/*********************************************************************** + * Enables + **********************************************************************/ +void wbx_base::wbx_version3::set_tx_enabled(bool enb){ + self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, + (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0); +} + + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +double wbx_base::wbx_version3::set_tx_gain(double gain, const std::string &name){ + assert_has(wbx_v3_tx_gain_ranges.keys(), name, "wbx tx gain name"); + if(name == "PGA0"){ + boost::uint16_t io_bits = tx_pga0_gain_to_iobits(gain); + + self_base->_tx_gains[name] = gain; + + //write the new gain to tx gpio outputs + //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); + } + else UHD_THROW_INVALID_CODE_PATH(); + return self_base->_tx_gains[name]; //shadow +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { + //clip to tuning range + target_freq = wbx_v3_freq_range.clip(target_freq); + + UHD_LOGV(often) << boost::format( + "WBX tune: target frequency %f Mhz" + ) % (target_freq/1e6) << std::endl; + + //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) + static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of + (0,23) //adf4350_regs_t::PRESCALER_4_5 + (1,75) //adf4350_regs_t::PRESCALER_8_9 + ; + + //map rf divider select output dividers to enums + static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of + (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) + (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) + (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) + (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) + (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) + ; + + double actual_freq, pfd_freq; + double ref_freq = self_base->get_iface()->get_clock_rate(unit); + int R=0, BS=0, N=0, FRAC=0, MOD=0; + int RFdiv = 1; + adf4350_regs_t::reference_divide_by_2_t T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + adf4350_regs_t::reference_doubler_t D = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; + + //Reference doubler for 50% duty cycle + // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 + if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; + + //increase RF divider until acceptable VCO frequency + //start with target_freq*2 because mixer has divide by 2 + double vco_freq = target_freq*2; + while (vco_freq < 2.2e9) { + vco_freq *= 2; + RFdiv *= 2; + } + + //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exists when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_rf = f_vco/RFdiv) + * f_actual = f_rf/2 + */ + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = ref_freq*(1+D)/(R*(1+T)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > 25e6) continue; + + //ignore fractional part of tuning + N = int(std::floor(vco_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < prescaler_to_min_int_div[prescaler]) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below 100KHz + //constraint on band select clock + if (pfd_freq/BS > 100e3) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = int((vco_freq/pfd_freq - N)*MOD); + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0){ + T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; + R /= 2; + } + + //actual frequency calculation + actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); + + + UHD_LOGV(often) + << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + + << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%s" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv % self_base->get_locked(unit).to_pp_string() << std::endl + << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" + ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + + //load the register values + adf4350_regs_t regs; + + regs.frac_12_bit = FRAC; + regs.int_16_bit = N; + regs.mod_12_bit = MOD; + regs.prescaler = prescaler; + regs.r_counter_10_bit = R; + regs.reference_divide_by_2 = T; + regs.reference_doubler = D; + regs.band_select_clock_div = BS; + UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); + regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + + if (unit == dboard_iface::UNIT_RX) { + freq_range_t rx_lo_5dbm = list_of + (range_t(0.05e9, 1.4e9)) + ; + + freq_range_t rx_lo_2dbm = list_of + (range_t(1.4e9, 2.2e9)) + ; + + if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + + if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; + + } else if (unit == dboard_iface::UNIT_TX) { + freq_range_t tx_lo_5dbm = list_of + (range_t(0.05e9, 1.7e9)) + (range_t(1.9e9, 2.2e9)) + ; + + freq_range_t tx_lo_m1dbm = list_of + (range_t(1.7e9, 1.9e9)) + ; + + if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + + if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM; + + } + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + int addr; + + for(addr=5; addr>=0; addr--){ + UHD_LOGV(often) << boost::format( + "WBX SPI Reg (0x%02x): 0x%08x" + ) % addr % regs.get_reg(addr) << std::endl; + self_base->get_iface()->write_spi( + unit, spi_config_t::EDGE_RISE, + regs.get_reg(addr), 32 + ); + } + + //return the actual frequency + UHD_LOGV(often) << boost::format( + "WBX tune: actual frequency %f Mhz" + ) % (actual_freq/1e6) << std::endl; + return actual_freq; +} diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp new file mode 100644 index 000000000..3a85826cd --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_version4.cpp @@ -0,0 +1,344 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "db_wbx_common.hpp" +#include "adf4351_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * WBX Version 3 Constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_v4_tx_gain_ranges = map_list_of + ("PGA0", gain_range_t(0, 31, 1.0)) +; + +static const freq_range_t wbx_v4_freq_range(50.0e6, 2.2e9); + + +/*********************************************************************** + * Gain-related functions + **********************************************************************/ +static int tx_pga0_gain_to_iobits(double &gain){ + //clip the input + gain = wbx_v4_tx_gain_ranges["PGA0"].clip(gain); + + //convert to attenuation + double attn = wbx_v4_tx_gain_ranges["PGA0"].stop() - gain; + + //calculate the attenuation + int attn_code = boost::math::iround(attn); + 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_v4_tx_gain_ranges["PGA0"].stop() - double(attn_code); + + return iobits; +} + + +/*********************************************************************** + * WBX Common Implementation + **********************************************************************/ +wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { + //register our handle on the primary wbx_base instance + self_base = _self_wbx_base; + + //////////////////////////////////////////////////////////////////// + // Register RX properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name").set("WBX RX v4"); + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); + this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); + + //////////////////////////////////////////////////////////////////// + // Register TX properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<std::string>("name").set("WBX TX v4"); + BOOST_FOREACH(const std::string &name, wbx_v4_tx_gain_ranges.keys()){ + self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name)) + .set(wbx_v4_tx_gain_ranges[name].start()); + self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(wbx_v4_tx_gain_ranges[name]); + } + this->get_tx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); + this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); + this->get_tx_subtree()->create<bool>("enabled") + .subscribe(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1)) + .set(true); //start enabled + + //set attenuator control bits + int v4_iobits = TX_ATTN_MASK; + int v4_tx_mod = ADF4351_PDBRF; + + //set the gpio directions and atr controls + self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v4_tx_mod|v4_iobits); + self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4351_PDBRF); + self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v4_tx_mod|v4_iobits); + self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4351_CE|RXBB_PDB|ADF4351_PDBRF|RX_ATTN_MASK); + + //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) + //set TX gain iobits to min gain (max attenuation) when RX_ONLY or IDLE to suppress LO leakage + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); + + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); +} + +wbx_base::wbx_version4::~wbx_version4(void){ + /* NOP */ +} + + +/*********************************************************************** + * Enables + **********************************************************************/ +void wbx_base::wbx_version4::set_tx_enabled(bool enb) { + self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, + (enb)? TX_POWER_UP | ADF4351_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0); +} + + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +double wbx_base::wbx_version4::set_tx_gain(double gain, const std::string &name) { + assert_has(wbx_v4_tx_gain_ranges.keys(), name, "wbx tx gain name"); + if(name == "PGA0"){ + boost::uint16_t io_bits = tx_pga0_gain_to_iobits(gain); + + self_base->_tx_gains[name] = gain; + + //write the new gain to tx gpio outputs + //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); + + } + else UHD_THROW_INVALID_CODE_PATH(); + return self_base->_tx_gains[name]; +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { + //clip to tuning range + target_freq = wbx_v4_freq_range.clip(target_freq); + + UHD_LOGV(often) << boost::format( + "WBX tune: target frequency %f Mhz" + ) % (target_freq/1e6) << std::endl; + + //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) + static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of + (0,23) //adf4351_regs_t::PRESCALER_4_5 + (1,75) //adf4351_regs_t::PRESCALER_8_9 + ; + + //map rf divider select output dividers to enums + static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of + (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) + (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) + (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) + (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) + (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) + (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) + (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) + ; + + double actual_freq, pfd_freq; + double ref_freq = self_base->get_iface()->get_clock_rate(unit); + int R=0, BS=0, N=0, FRAC=0, MOD=0; + int RFdiv = 1; + adf4351_regs_t::reference_divide_by_2_t T = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + adf4351_regs_t::reference_doubler_t D = adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; + + //Reference doubler for 50% duty cycle + // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 + if(ref_freq <= 12.5e6) D = adf4351_regs_t::REFERENCE_DOUBLER_ENABLED; + + //increase RF divider until acceptable VCO frequency + //start with target_freq*2 because mixer has divide by 2 + double vco_freq = target_freq*2; + while (vco_freq < 2.2e9) { + vco_freq *= 2; + RFdiv *= 2; + } + + //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + adf4351_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exits when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_rf = f_vco/RFdiv) + * f_actual = f_rf/2 + */ + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = ref_freq*(1+D)/(R*(1+T)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > 25e6) continue; + + //ignore fractional part of tuning + N = int(std::floor(vco_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < prescaler_to_min_int_div[prescaler]) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below 100KHz + //constraint on band select clock + if (pfd_freq/BS > 100e3) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = int((vco_freq/pfd_freq - N)*MOD); + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0){ + T = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; + R /= 2; + } + + //actual frequency calculation + actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); + + + UHD_LOGV(often) + << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + + << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%s" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv % self_base->get_locked(unit).to_pp_string() << std::endl + << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" + ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + + //load the register values + adf4351_regs_t regs; + + regs.frac_12_bit = FRAC; + regs.int_16_bit = N; + regs.mod_12_bit = MOD; + regs.prescaler = prescaler; + regs.r_counter_10_bit = R; + regs.reference_divide_by_2 = T; + regs.reference_doubler = D; + regs.band_select_clock_div = BS; + UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); + regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + + if (unit == dboard_iface::UNIT_RX) { + freq_range_t rx_lo_5dbm = list_of + (range_t(0.05e9, 1.4e9)) + ; + + freq_range_t rx_lo_2dbm = list_of + (range_t(1.4e9, 2.2e9)) + ; + + if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; + + if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM; + + } else if (unit == dboard_iface::UNIT_TX) { + freq_range_t tx_lo_5dbm = list_of + (range_t(0.05e9, 1.7e9)) + (range_t(1.9e9, 2.2e9)) + ; + + freq_range_t tx_lo_m1dbm = list_of + (range_t(1.7e9, 1.9e9)) + ; + + if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; + + if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_M1DBM; + + } + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + int addr; + + for(addr=5; addr>=0; addr--){ + UHD_LOGV(often) << boost::format( + "WBX SPI Reg (0x%02x): 0x%08x" + ) % addr % regs.get_reg(addr) << std::endl; + self_base->get_iface()->write_spi( + unit, spi_config_t::EDGE_RISE, + regs.get_reg(addr), 32 + ); + } + + //return the actual frequency + UHD_LOGV(often) << boost::format( + "WBX tune: actual frequency %f Mhz" + ) % (actual_freq/1e6) << std::endl; + return actual_freq; +} diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp new file mode 100644 index 000000000..0fdad0d40 --- /dev/null +++ b/host/lib/usrp/dboard/db_xcvr2450.cpp @@ -0,0 +1,673 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// TX IO Pins +#define HB_PA_OFF_TXIO (1 << 15) // 5GHz PA, 1 = off, 0 = on +#define LB_PA_OFF_TXIO (1 << 14) // 2.4GHz PA, 1 = off, 0 = on +#define ANTSEL_TX1_RX2_TXIO (1 << 13) // 1 = Ant 1 to TX, Ant 2 to RX +#define ANTSEL_TX2_RX1_TXIO (1 << 12) // 1 = Ant 2 to TX, Ant 1 to RX +#define TX_EN_TXIO (1 << 11) // 1 = TX on, 0 = TX off +#define AD9515DIV_TXIO (1 << 4) // 1 = Div by 3, 0 = Div by 2 + +#define TXIO_MASK (HB_PA_OFF_TXIO | LB_PA_OFF_TXIO | ANTSEL_TX1_RX2_TXIO | ANTSEL_TX2_RX1_TXIO | TX_EN_TXIO | AD9515DIV_TXIO) + +// TX IO Functions +#define HB_PA_TXIO LB_PA_OFF_TXIO +#define LB_PA_TXIO HB_PA_OFF_TXIO +#define TX_ENB_TXIO TX_EN_TXIO +#define TX_DIS_TXIO 0 +#define AD9515DIV_3_TXIO AD9515DIV_TXIO +#define AD9515DIV_2_TXIO 0 + +// RX IO Pins +#define LOCKDET_RXIO (1 << 15) // This is an INPUT!!! +#define POWER_RXIO (1 << 14) // 1 = power on, 0 = shutdown +#define RX_EN_RXIO (1 << 13) // 1 = RX on, 0 = RX off +#define RX_HP_RXIO (1 << 12) // 0 = Fc set by rx_hpf, 1 = 600 KHz + +#define RXIO_MASK (POWER_RXIO | RX_EN_RXIO | RX_HP_RXIO) + +// RX IO Functions +#define POWER_UP_RXIO POWER_RXIO +#define POWER_DOWN_RXIO 0 +#define RX_ENB_RXIO RX_EN_RXIO +#define RX_DIS_RXIO 0 + +#include "max2829_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The XCVR 2450 constants + **********************************************************************/ +static const freq_range_t xcvr_freq_range = list_of + (range_t(2.4e9, 2.5e9)) + (range_t(4.9e9, 6.0e9)) +; + +//Multiplied by 2.0 for conversion to complex bandpass from lowpass +static const freq_range_t xcvr_tx_bandwidth_range = list_of + (range_t(2.0*12e6)) + (range_t(2.0*18e6)) + (range_t(2.0*24e6)) +; + +//Multiplied by 2.0 for conversion to complex bandpass from lowpass +static const freq_range_t xcvr_rx_bandwidth_range = list_of + (range_t(2.0*0.9*7.5e6, 2.0*1.1*7.5e6)) + (range_t(2.0*0.9*9.5e6, 2.0*1.1*9.5e6)) + (range_t(2.0*0.9*14e6, 2.0*1.1*14e6)) + (range_t(2.0*0.9*18e6, 2.0*1.1*18e6)) +; + +static const std::vector<std::string> xcvr_antennas = list_of("J1")("J2"); + +static const uhd::dict<std::string, gain_range_t> xcvr_tx_gain_ranges = map_list_of + ("VGA", gain_range_t(0, 30, 0.5)) + ("BB", gain_range_t(0, 5, 1.5)) +; +static const uhd::dict<std::string, gain_range_t> xcvr_rx_gain_ranges = map_list_of + ("LNA", gain_range_t(list_of + (range_t(0)) + (range_t(15)) + (range_t(30.5)) + )) + ("VGA", gain_range_t(0, 62, 2.0)) +; + +/*********************************************************************** + * The XCVR 2450 dboard class + **********************************************************************/ +class xcvr2450 : public xcvr_dboard_base{ +public: + xcvr2450(ctor_args_t args); + ~xcvr2450(void); + +private: + double _lo_freq; + double _rx_bandwidth, _tx_bandwidth; + uhd::dict<std::string, double> _tx_gains, _rx_gains; + std::string _tx_ant, _rx_ant; + int _ad9515div; + max2829_regs_t _max2829_regs; + + double set_lo_freq(double target_freq); + double set_lo_freq_core(double target_freq); + void set_tx_ant(const std::string &ant); + void set_rx_ant(const std::string &ant); + double set_tx_gain(double gain, const std::string &name); + double set_rx_gain(double gain, const std::string &name); + double set_rx_bandwidth(double bandwidth); + double set_tx_bandwidth(double bandwidth); + + void update_atr(void); + void spi_reset(void); + void send_reg(boost::uint8_t addr){ + boost::uint32_t value = _max2829_regs.get_reg(addr); + UHD_LOGV(often) << boost::format( + "XCVR2450: send reg 0x%02x, value 0x%05x" + ) % int(addr) % value << std::endl; + this->get_iface()->write_spi( + dboard_iface::UNIT_RX, + spi_config_t::EDGE_RISE, + value, 24 + ); + } + + static bool is_highband(double freq){return freq > 3e9;} + + /*! + * Get the lock detect status of the LO. + * \return sensor for locked + */ + sensor_value_t get_locked(void){ + const bool locked = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & LOCKDET_RXIO) != 0; + return sensor_value_t("LO", locked, "locked", "unlocked"); + } + + /*! + * Read the RSSI from the aux adc + * \return the rssi sensor in dBm + */ + sensor_value_t get_rssi(void){ + //*FIXME* RSSI depends on LNA Gain Setting (datasheet pg 16 top middle chart) + double max_power = 0.0; + switch(_max2829_regs.rx_lna_gain){ + case 0: + case 1: max_power = 0; break; + case 2: max_power = -15; break; + case 3: max_power = -30.5; break; + } + + //constants for the rssi calculation + static const double min_v = 2.5, max_v = 0.5; + static const double rssi_dyn_range = 60.0; + //calculate the rssi from the voltage + double voltage = this->get_iface()->read_aux_adc(dboard_iface::UNIT_RX, dboard_iface::AUX_ADC_B); + double rssi = max_power - rssi_dyn_range*(voltage - min_v)/(max_v - min_v); + return sensor_value_t("RSSI", rssi, "dBm"); + } +}; + +/*********************************************************************** + * Register the XCVR 2450 dboard + **********************************************************************/ +static dboard_base::sptr make_xcvr2450(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new xcvr2450(args)); +} + +UHD_STATIC_BLOCK(reg_xcvr2450_dboard){ + //register the factory function for the rx and tx dbids + dboard_manager::register_dboard(0x0061, 0x0060, &make_xcvr2450, "XCVR2450"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ + spi_reset(); //prepare the spi + + _rx_bandwidth = 9.5e6; + _tx_bandwidth = 12.0e6; + + //setup the misc max2829 registers + _max2829_regs.mimo_select = max2829_regs_t::MIMO_SELECT_MIMO; + _max2829_regs.band_sel_mimo = max2829_regs_t::BAND_SEL_MIMO_MIMO; + _max2829_regs.pll_cp_select = max2829_regs_t::PLL_CP_SELECT_4MA; + _max2829_regs.rssi_high_bw = max2829_regs_t::RSSI_HIGH_BW_6MHZ; + _max2829_regs.tx_lpf_coarse_adj = max2829_regs_t::TX_LPF_COARSE_ADJ_12MHZ; + _max2829_regs.rx_lpf_coarse_adj = max2829_regs_t::RX_LPF_COARSE_ADJ_9_5MHZ; + _max2829_regs.rx_lpf_fine_adj = max2829_regs_t::RX_LPF_FINE_ADJ_100; + _max2829_regs.rx_vga_gain_spi = max2829_regs_t::RX_VGA_GAIN_SPI_SPI; + _max2829_regs.rssi_output_range = max2829_regs_t::RSSI_OUTPUT_RANGE_HIGH; + _max2829_regs.rssi_op_mode = max2829_regs_t::RSSI_OP_MODE_ENABLED; + _max2829_regs.rssi_pin_fcn = max2829_regs_t::RSSI_PIN_FCN_RSSI; + _max2829_regs.rx_highpass = max2829_regs_t::RX_HIGHPASS_100HZ; + _max2829_regs.tx_vga_gain_spi = max2829_regs_t::TX_VGA_GAIN_SPI_SPI; + _max2829_regs.pa_driver_linearity = max2829_regs_t::PA_DRIVER_LINEARITY_78; + _max2829_regs.tx_vga_linearity = max2829_regs_t::TX_VGA_LINEARITY_78; + _max2829_regs.tx_upconv_linearity = max2829_regs_t::TX_UPCONV_LINEARITY_78; + + //send initial register settings + for(boost::uint8_t reg = 0x2; reg <= 0xC; reg++){ + this->send_reg(reg); + } + + //////////////////////////////////////////////////////////////////// + // Register RX properties + //////////////////////////////////////////////////////////////////// + this->get_rx_subtree()->create<std::string>("name") + .set(get_rx_id().to_pp_string()); + this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&xcvr2450::get_locked, this)); + this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi") + .publish(boost::bind(&xcvr2450::get_rssi, this)); + BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){ + this->get_rx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&xcvr2450::set_rx_gain, this, _1, name)) + .set(xcvr_rx_gain_ranges[name].start()); + this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(xcvr_rx_gain_ranges[name]); + } + this->get_rx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1)) + .set(double(2.45e9)); + this->get_rx_subtree()->create<meta_range_t>("freq/range") + .set(xcvr_freq_range); + this->get_rx_subtree()->create<std::string>("antenna/value") + .subscribe(boost::bind(&xcvr2450::set_rx_ant, this, _1)) + .set(xcvr_antennas.at(0)); + this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(xcvr_antennas); + this->get_rx_subtree()->create<std::string>("connection") + .set("IQ"); + this->get_rx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + this->get_rx_subtree()->create<double>("bandwidth/value") + .coerce(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth + .set(2.0*_rx_bandwidth); //_rx_bandwidth in lowpass, convert to complex bandpass + this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(xcvr_rx_bandwidth_range); + + //////////////////////////////////////////////////////////////////// + // Register TX properties + //////////////////////////////////////////////////////////////////// + this->get_tx_subtree()->create<std::string>("name") + .set(get_tx_id().to_pp_string()); + this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .publish(boost::bind(&xcvr2450::get_locked, this)); + BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){ + this->get_tx_subtree()->create<double>("gains/"+name+"/value") + .coerce(boost::bind(&xcvr2450::set_tx_gain, this, _1, name)) + .set(xcvr_tx_gain_ranges[name].start()); + this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") + .set(xcvr_tx_gain_ranges[name]); + } + this->get_tx_subtree()->create<double>("freq/value") + .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1)) + .set(double(2.45e9)); + this->get_tx_subtree()->create<meta_range_t>("freq/range") + .set(xcvr_freq_range); + this->get_tx_subtree()->create<std::string>("antenna/value") + .subscribe(boost::bind(&xcvr2450::set_tx_ant, this, _1)) + .set(xcvr_antennas.at(1)); + this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(xcvr_antennas); + this->get_tx_subtree()->create<std::string>("connection") + .set("QI"); + this->get_tx_subtree()->create<bool>("enabled") + .set(true); //always enabled + this->get_tx_subtree()->create<bool>("use_lo_offset") + .set(true); + this->get_tx_subtree()->create<double>("bandwidth/value") + .coerce(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth + .set(2.0*_tx_bandwidth); //_tx_bandwidth in lowpass, convert to complex bandpass + this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") + .set(xcvr_tx_bandwidth_range); + + //enable only the clocks we need + this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); + + //set the gpio directions and atr controls (identically) + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); + this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TXIO_MASK); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RXIO_MASK); +} + +xcvr2450::~xcvr2450(void){ + UHD_SAFE_CALL(spi_reset();) +} + +void xcvr2450::spi_reset(void){ + //spi reset mode: global enable = off, tx and rx enable = on + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_ENB_TXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + + //take it back out of spi reset mode and wait a bit + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +} + +/*********************************************************************** + * Update ATR regs which change with Antenna or Freq + **********************************************************************/ +void xcvr2450::update_atr(void){ + //calculate tx atr pins + int band_sel = (xcvr2450::is_highband(_lo_freq))? HB_PA_TXIO : LB_PA_TXIO; + int tx_ant_sel = (_tx_ant == "J1")? ANTSEL_TX1_RX2_TXIO : ANTSEL_TX2_RX1_TXIO; + int rx_ant_sel = (_rx_ant == "J2")? ANTSEL_TX1_RX2_TXIO : ANTSEL_TX2_RX1_TXIO; + int xx_ant_sel = tx_ant_sel; //Prefer the tx antenna selection for full duplex, + //due to the issue that USRP1 will take the value of full duplex for its TXATR. + int ad9515div = (_ad9515div == 3)? AD9515DIV_3_TXIO : AD9515DIV_2_TXIO; + + //set the tx registers + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); + + //set the rx registers + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +double xcvr2450::set_lo_freq(double target_freq){ + //tune the LO and sleep a bit for lock + //if not locked, try some carrier offsets + double actual = 0.0; + for (double offset = 0.0; offset <= 3e6; offset+=1e6){ + actual = this->set_lo_freq_core(target_freq + offset); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (this->get_locked().to_bool()) break; + } + return actual; +} + +double xcvr2450::set_lo_freq_core(double target_freq){ + + //clip the input to the range + target_freq = xcvr_freq_range.clip(target_freq); + + //variables used in the calculation below + double scaler = xcvr2450::is_highband(target_freq)? (4.0/5.0) : (4.0/3.0); + double ref_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_TX); + int R, intdiv, fracdiv; + + //loop through values until we get a match + for(_ad9515div = 2; _ad9515div <= 3; _ad9515div++){ + for(R = 1; R <= 7; R++){ + double N = (target_freq*scaler*R*_ad9515div)/ref_freq; + intdiv = int(std::floor(N)); + fracdiv = boost::math::iround((N - intdiv)*double(1 << 16)); + //actual minimum is 128, but most chips seems to require higher to lock + if (intdiv < 131 or intdiv > 255) continue; + //constraints met: exit loop + goto done_loop; + } + } done_loop: + + //calculate the actual freq from the values above + double N = double(intdiv) + double(fracdiv)/double(1 << 16); + _lo_freq = (N*ref_freq)/(scaler*R*_ad9515div); + + UHD_LOGV(often) + << boost::format("XCVR2450 tune:\n") + << boost::format(" R=%d, N=%f, ad9515=%d, scaler=%f\n") % R % N % _ad9515div % scaler + << boost::format(" Ref Freq=%fMHz\n") % (ref_freq/1e6) + << boost::format(" Target Freq=%fMHz\n") % (target_freq/1e6) + << boost::format(" Actual Freq=%fMHz\n") % (_lo_freq/1e6) + << std::endl; + + //high-high band or low-high band? + if(_lo_freq > (5.35e9 + 5.47e9)/2.0){ + UHD_LOGV(often) << "XCVR2450 tune: Using high-high band" << std::endl; + _max2829_regs.band_select_802_11a = max2829_regs_t::BAND_SELECT_802_11A_5_47GHZ_TO_5_875GHZ; + }else{ + UHD_LOGV(often) << "XCVR2450 tune: Using low-high band" << std::endl; + _max2829_regs.band_select_802_11a = max2829_regs_t::BAND_SELECT_802_11A_4_9GHZ_TO_5_35GHZ; + } + + //new band select settings and ad9515 divider + this->update_atr(); + + //load new counters into registers + _max2829_regs.int_div_ratio_word = intdiv; + _max2829_regs.frac_div_ratio_lsb = fracdiv & 0x3; + _max2829_regs.frac_div_ratio_msb = fracdiv >> 2; + this->send_reg(0x3); //integer + this->send_reg(0x4); //fractional + + //load the reference divider and band select into registers + //toggle the bandswitch from off to automatic (which really means start) + _max2829_regs.ref_divider = R; + _max2829_regs.band_select = (xcvr2450::is_highband(_lo_freq))? + max2829_regs_t::BAND_SELECT_5GHZ : + max2829_regs_t::BAND_SELECT_2_4GHZ ; + _max2829_regs.vco_bandswitch = max2829_regs_t::VCO_BANDSWITCH_DISABLE; + this->send_reg(0x5); + _max2829_regs.vco_bandswitch = max2829_regs_t::VCO_BANDSWITCH_AUTOMATIC;; + this->send_reg(0x5); + + return _lo_freq; +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void xcvr2450::set_tx_ant(const std::string &ant){ + assert_has(xcvr_antennas, ant, "xcvr antenna name"); + _tx_ant = ant; + this->update_atr(); //sets the atr to the new antenna setting +} + +void xcvr2450::set_rx_ant(const std::string &ant){ + assert_has(xcvr_antennas, ant, "xcvr antenna name"); + _rx_ant = ant; + this->update_atr(); //sets the atr to the new antenna setting +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Convert a requested gain for the tx vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 6 bit the register value + */ +static int gain_to_tx_vga_reg(double &gain){ + //calculate the register value + int reg = uhd::clip(boost::math::iround(gain*60/30.0) + 3, 0, 63); + + //calculate the actual gain value + if (reg < 4) gain = 0; + else if (reg < 48) gain = double(reg/2 - 1); + else gain = double(reg/2.0 - 1.5); + + //return register value + return reg; +} + +/*! + * Convert a requested gain for the tx bb into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return gain enum value + */ +static max2829_regs_t::tx_baseband_gain_t gain_to_tx_bb_reg(double &gain){ + int reg = uhd::clip(boost::math::iround(gain*3/5.0), 0, 3); + switch(reg){ + case 0: + gain = 0; + return max2829_regs_t::TX_BASEBAND_GAIN_0DB; + case 1: + gain = 2; + return max2829_regs_t::TX_BASEBAND_GAIN_2DB; + case 2: + gain = 3.5; + return max2829_regs_t::TX_BASEBAND_GAIN_3_5DB; + case 3: + gain = 5; + return max2829_regs_t::TX_BASEBAND_GAIN_5DB; + } + UHD_THROW_INVALID_CODE_PATH(); +} + +/*! + * Convert a requested gain for the rx vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 5 bit the register value + */ +static int gain_to_rx_vga_reg(double &gain){ + int reg = uhd::clip(boost::math::iround(gain/2.0), 0, 31); + gain = double(reg*2); + return reg; +} + +/*! + * Convert a requested gain for the rx lna into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 2 bit the register value + */ +static int gain_to_rx_lna_reg(double &gain){ + int reg = uhd::clip(boost::math::iround(gain*2/30.5) + 1, 0, 3); + switch(reg){ + case 0: + case 1: gain = 0; break; + case 2: gain = 15; break; + case 3: gain = 30.5; break; + } + return reg; +} + +double xcvr2450::set_tx_gain(double gain, const std::string &name){ + assert_has(xcvr_tx_gain_ranges.keys(), name, "xcvr tx gain name"); + if (name == "VGA"){ + _max2829_regs.tx_vga_gain = gain_to_tx_vga_reg(gain); + send_reg(0xC); + } + else if(name == "BB"){ + _max2829_regs.tx_baseband_gain = gain_to_tx_bb_reg(gain); + send_reg(0x9); + } + else UHD_THROW_INVALID_CODE_PATH(); + _tx_gains[name] = gain; + + return gain; +} + +double xcvr2450::set_rx_gain(double gain, const std::string &name){ + assert_has(xcvr_rx_gain_ranges.keys(), name, "xcvr rx gain name"); + if (name == "VGA"){ + _max2829_regs.rx_vga_gain = gain_to_rx_vga_reg(gain); + send_reg(0xB); + } + else if(name == "LNA"){ + _max2829_regs.rx_lna_gain = gain_to_rx_lna_reg(gain); + send_reg(0xB); + } + else UHD_THROW_INVALID_CODE_PATH(); + _rx_gains[name] = gain; + + return gain; +} + + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +static max2829_regs_t::tx_lpf_coarse_adj_t bandwidth_to_tx_lpf_coarse_reg(double &bandwidth){ + int reg = uhd::clip(boost::math::iround((bandwidth-6.0e6)/6.0e6), 1, 3); + + switch(reg){ + case 1: // bandwidth < 15MHz + bandwidth = 12e6; + return max2829_regs_t::TX_LPF_COARSE_ADJ_12MHZ; + case 2: // 15MHz < bandwidth < 21MHz + bandwidth = 18e6; + return max2829_regs_t::TX_LPF_COARSE_ADJ_18MHZ; + case 3: // bandwidth > 21MHz + bandwidth = 24e6; + return max2829_regs_t::TX_LPF_COARSE_ADJ_24MHZ; + } + UHD_THROW_INVALID_CODE_PATH(); +} + +static max2829_regs_t::rx_lpf_fine_adj_t bandwidth_to_rx_lpf_fine_reg(double &bandwidth, double requested_bandwidth){ + int reg = uhd::clip(boost::math::iround((requested_bandwidth/bandwidth)/0.05), 18, 22); + + switch(reg){ + case 18: // requested_bandwidth < 92.5% + bandwidth = 0.9 * bandwidth; + return max2829_regs_t::RX_LPF_FINE_ADJ_90; + case 19: // 92.5% < requested_bandwidth < 97.5% + bandwidth = 0.95 * bandwidth; + return max2829_regs_t::RX_LPF_FINE_ADJ_95; + case 20: // 97.5% < requested_bandwidth < 102.5% + bandwidth = 1.0 * bandwidth; + return max2829_regs_t::RX_LPF_FINE_ADJ_100; + case 21: // 102.5% < requested_bandwidth < 107.5% + bandwidth = 1.05 * bandwidth; + return max2829_regs_t::RX_LPF_FINE_ADJ_105; + case 22: // 107.5% < requested_bandwidth + bandwidth = 1.1 * bandwidth; + return max2829_regs_t::RX_LPF_FINE_ADJ_110; + } + UHD_THROW_INVALID_CODE_PATH(); +} + +static max2829_regs_t::rx_lpf_coarse_adj_t bandwidth_to_rx_lpf_coarse_reg(double &bandwidth){ + int reg = uhd::clip(boost::math::iround((bandwidth-7.0e6)/1.0e6), 0, 11); + + switch(reg){ + case 0: // bandwidth < 7.5MHz + case 1: // 7.5MHz < bandwidth < 8.5MHz + bandwidth = 7.5e6; + return max2829_regs_t::RX_LPF_COARSE_ADJ_7_5MHZ; + case 2: // 8.5MHz < bandwidth < 9.5MHz + case 3: // 9.5MHz < bandwidth < 10.5MHz + case 4: // 10.5MHz < bandwidth < 11.5MHz + bandwidth = 9.5e6; + return max2829_regs_t::RX_LPF_COARSE_ADJ_9_5MHZ; + case 5: // 11.5MHz < bandwidth < 12.5MHz + case 6: // 12.5MHz < bandwidth < 13.5MHz + case 7: // 13.5MHz < bandwidth < 14.5MHz + case 8: // 14.5MHz < bandwidth < 15.5MHz + bandwidth = 14e6; + return max2829_regs_t::RX_LPF_COARSE_ADJ_14MHZ; + case 9: // 15.5MHz < bandwidth < 16.5MHz + case 10: // 16.5MHz < bandwidth < 17.5MHz + case 11: // 17.5MHz < bandwidth + bandwidth = 18e6; + return max2829_regs_t::RX_LPF_COARSE_ADJ_18MHZ; + } + UHD_THROW_INVALID_CODE_PATH(); +} + +double xcvr2450::set_rx_bandwidth(double bandwidth){ + double requested_bandwidth = bandwidth; + + //convert complex bandpass to lowpass bandwidth + bandwidth = bandwidth/2.0; + + //compute coarse low pass cutoff frequency setting + _max2829_regs.rx_lpf_coarse_adj = bandwidth_to_rx_lpf_coarse_reg(bandwidth); + + //compute fine low pass cutoff frequency setting + _max2829_regs.rx_lpf_fine_adj = bandwidth_to_rx_lpf_fine_reg(bandwidth, requested_bandwidth); + + //shadow bandwidth setting + _rx_bandwidth = bandwidth; + + //update register + send_reg(0x7); + + UHD_LOGV(often) << boost::format( + "XCVR2450 RX Bandwidth (lp_fc): %f Hz, coarse reg: %d, fine reg: %d" + ) % _rx_bandwidth % (int(_max2829_regs.rx_lpf_coarse_adj)) % (int(_max2829_regs.rx_lpf_fine_adj)) << std::endl; + + return 2.0*_rx_bandwidth; +} + +double xcvr2450::set_tx_bandwidth(double bandwidth){ + //convert complex bandpass to lowpass bandwidth + bandwidth = bandwidth/2.0; + + //compute coarse low pass cutoff frequency setting + _max2829_regs.tx_lpf_coarse_adj = bandwidth_to_tx_lpf_coarse_reg(bandwidth); + + //shadow bandwidth setting + _tx_bandwidth = bandwidth; + + //update register + send_reg(0x7); + + UHD_LOGV(often) << boost::format( + "XCVR2450 TX Bandwidth (lp_fc): %f Hz, coarse reg: %d" + ) % _tx_bandwidth % (int(_max2829_regs.tx_lpf_coarse_adj)) << std::endl; + + //convert lowpass back to complex bandpass bandwidth + return 2.0*_tx_bandwidth; +} diff --git a/host/lib/usrp/dboard_base.cpp b/host/lib/usrp/dboard_base.cpp new file mode 100644 index 000000000..fe14c02b9 --- /dev/null +++ b/host/lib/usrp/dboard_base.cpp @@ -0,0 +1,100 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dboard_ctor_args.hpp" +#include <uhd/usrp/dboard_base.hpp> +#include <boost/format.hpp> +#include <stdexcept> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * dboard_base dboard dboard_base class + **********************************************************************/ +struct dboard_base::impl{ + dboard_ctor_args_t args; +}; + +dboard_base::dboard_base(ctor_args_t args){ + _impl = UHD_PIMPL_MAKE(impl, ()); + _impl->args = *static_cast<dboard_ctor_args_t *>(args); +} + +std::string dboard_base::get_subdev_name(void){ + return _impl->args.sd_name; +} + +dboard_iface::sptr dboard_base::get_iface(void){ + return _impl->args.db_iface; +} + +dboard_id_t dboard_base::get_rx_id(void){ + return _impl->args.rx_id; +} + +dboard_id_t dboard_base::get_tx_id(void){ + return _impl->args.tx_id; +} + +property_tree::sptr dboard_base::get_rx_subtree(void){ + return _impl->args.rx_subtree; +} + +property_tree::sptr dboard_base::get_tx_subtree(void){ + return _impl->args.tx_subtree; +} + +/*********************************************************************** + * xcvr dboard dboard_base class + **********************************************************************/ +xcvr_dboard_base::xcvr_dboard_base(ctor_args_t args) : dboard_base(args){ + if (get_rx_id() == dboard_id_t::none()){ + throw uhd::runtime_error(str(boost::format( + "cannot create xcvr board when the rx id is \"%s\"" + ) % dboard_id_t::none().to_pp_string())); + } + if (get_tx_id() == dboard_id_t::none()){ + throw uhd::runtime_error(str(boost::format( + "cannot create xcvr board when the tx id is \"%s\"" + ) % dboard_id_t::none().to_pp_string())); + } +} + +/*********************************************************************** + * rx dboard dboard_base class + **********************************************************************/ +rx_dboard_base::rx_dboard_base(ctor_args_t args) : dboard_base(args){ + if (get_tx_id() != dboard_id_t::none()){ + throw uhd::runtime_error(str(boost::format( + "cannot create rx board when the tx id is \"%s\"" + " -> expected a tx id of \"%s\"" + ) % get_tx_id().to_pp_string() % dboard_id_t::none().to_pp_string())); + } +} + +/*********************************************************************** + * tx dboard dboard_base class + **********************************************************************/ +tx_dboard_base::tx_dboard_base(ctor_args_t args) : dboard_base(args){ + if (get_rx_id() != dboard_id_t::none()){ + throw uhd::runtime_error(str(boost::format( + "cannot create tx board when the rx id is \"%s\"" + " -> expected a rx id of \"%s\"" + ) % get_rx_id().to_pp_string() % dboard_id_t::none().to_pp_string())); + } +} diff --git a/host/lib/usrp/dboard_ctor_args.hpp b/host/lib/usrp/dboard_ctor_args.hpp new file mode 100644 index 000000000..99c071ff8 --- /dev/null +++ b/host/lib/usrp/dboard_ctor_args.hpp @@ -0,0 +1,38 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP + +#include <uhd/property_tree.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + + struct dboard_ctor_args_t{ + std::string sd_name; + dboard_iface::sptr db_iface; + dboard_id_t rx_id, tx_id; + property_tree::sptr rx_subtree, tx_subtree; + }; + +}} //namespace + +#endif /* INCLUDED_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP */ diff --git a/host/lib/usrp/dboard_eeprom.cpp b/host/lib/usrp/dboard_eeprom.cpp new file mode 100644 index 000000000..f2bee47a9 --- /dev/null +++ b/host/lib/usrp/dboard_eeprom.cpp @@ -0,0 +1,169 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <algorithm> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Utility functions + **********************************************************************/ + +//! create a string from a byte vector, return empty if invalid ascii +static const std::string bytes_to_string(const byte_vector_t &bytes){ + std::string out; + BOOST_FOREACH(boost::uint8_t byte, bytes){ + if (byte < 32 or byte > 127) return out; + out += byte; + } + return out; +} + +//! create a byte vector from a string, null terminate unless max length +static const byte_vector_t string_to_bytes(const std::string &string, size_t max_length){ + byte_vector_t bytes; + for (size_t i = 0; i < std::min(string.size(), max_length); i++){ + bytes.push_back(string[i]); + } + if (bytes.size() < max_length - 1) bytes.push_back('\0'); + return bytes; +} + +//////////////////////////////////////////////////////////////////////// +// format of daughterboard EEPROM +// 00: 0xDB code for ``I'm a daughterboard'' +// 01: .. Daughterboard ID (LSB) +// 02: .. Daughterboard ID (MSB) +// 03: .. io bits 7-0 direction (bit set if it's an output from m'board) +// 04: .. io bits 15-8 direction (bit set if it's an output from m'board) +// 05: .. ADC0 DC offset correction (LSB) +// 06: .. ADC0 DC offset correction (MSB) +// 07: .. ADC1 DC offset correction (LSB) +// 08: .. ADC1 DC offset correction (MSB) +// ... +// 1f: .. negative of the sum of bytes [0x00, 0x1e] + +#define DB_EEPROM_MAGIC 0x00 +#define DB_EEPROM_MAGIC_VALUE 0xDB +#define DB_EEPROM_ID_LSB 0x01 +#define DB_EEPROM_ID_MSB 0x02 +#define DB_EEPROM_REV_LSB 0x03 +#define DB_EEPROM_REV_MSB 0x04 +#define DB_EEPROM_OFFSET_0_LSB 0x05 // offset correction for ADC or DAC 0 +#define DB_EEPROM_OFFSET_0_MSB 0x06 +#define DB_EEPROM_OFFSET_1_LSB 0x07 // offset correction for ADC or DAC 1 +#define DB_EEPROM_OFFSET_1_MSB 0x08 +#define DB_EEPROM_SERIAL 0x09 +#define DB_EEPROM_SERIAL_LEN 0x09 //9 ASCII characters +#define DB_EEPROM_CHKSUM 0x1f + +#define DB_EEPROM_CLEN 0x20 // length of common portion of eeprom + +#define DB_EEPROM_CUSTOM_BASE DB_EEPROM_CLEN // first avail offset for + // daughterboard specific use +//////////////////////////////////////////////////////////////////////// + +//negative sum of bytes excluding checksum byte +static boost::uint8_t checksum(const byte_vector_t &bytes){ + int sum = 0; + for (size_t i = 0; i < std::min(bytes.size(), size_t(DB_EEPROM_CHKSUM)); i++){ + sum -= int(bytes.at(i)); + } + UHD_LOGV(often) << boost::format("sum: 0x%02x") % sum << std::endl; + return boost::uint8_t(sum); +} + +dboard_eeprom_t::dboard_eeprom_t(void){ + id = dboard_id_t::none(); + serial = ""; +} + +void dboard_eeprom_t::load(i2c_iface &iface, boost::uint8_t addr){ + byte_vector_t bytes = iface.read_eeprom(addr, 0, DB_EEPROM_CLEN); + + std::ostringstream ss; + for (size_t i = 0; i < bytes.size(); i++){ + ss << boost::format( + "eeprom byte[0x%02x] = 0x%02x") % i % int(bytes.at(i) + ) << std::endl; + } + UHD_LOGV(often) << ss.str() << std::endl; + + try{ + UHD_ASSERT_THROW(bytes.size() >= DB_EEPROM_CLEN); + UHD_ASSERT_THROW(bytes[DB_EEPROM_MAGIC] == DB_EEPROM_MAGIC_VALUE); + UHD_ASSERT_THROW(bytes[DB_EEPROM_CHKSUM] == checksum(bytes)); + + //parse the ids + id = dboard_id_t::from_uint16(0 + | (boost::uint16_t(bytes[DB_EEPROM_ID_LSB]) << 0) + | (boost::uint16_t(bytes[DB_EEPROM_ID_MSB]) << 8) + ); + + //parse the serial + serial = bytes_to_string( + byte_vector_t(&bytes.at(DB_EEPROM_SERIAL), + &bytes.at(DB_EEPROM_SERIAL+DB_EEPROM_SERIAL_LEN)) + ); + + //parse the revision + const boost::uint16_t rev_num = 0 + | (boost::uint16_t(bytes[DB_EEPROM_REV_LSB]) << 0) + | (boost::uint16_t(bytes[DB_EEPROM_REV_MSB]) << 8) + ; + if (rev_num != 0 and rev_num != 0xffff){ + revision = boost::lexical_cast<std::string>(rev_num); + } + + }catch(const uhd::assertion_error &){ + id = dboard_id_t::none(); + serial = ""; + } +} + +void dboard_eeprom_t::store(i2c_iface &iface, boost::uint8_t addr) const{ + byte_vector_t bytes(DB_EEPROM_CLEN, 0); //defaults to all zeros + bytes[DB_EEPROM_MAGIC] = DB_EEPROM_MAGIC_VALUE; + + //load the id bytes + bytes[DB_EEPROM_ID_LSB] = boost::uint8_t(id.to_uint16() >> 0); + bytes[DB_EEPROM_ID_MSB] = boost::uint8_t(id.to_uint16() >> 8); + + //load the serial bytes + byte_vector_t ser_bytes = string_to_bytes(serial, DB_EEPROM_SERIAL_LEN); + std::copy(ser_bytes.begin(), ser_bytes.end(), &bytes.at(DB_EEPROM_SERIAL)); + + //load the revision bytes + if (not revision.empty()){ + const boost::uint16_t rev_num = boost::lexical_cast<boost::uint16_t>(revision); + bytes[DB_EEPROM_REV_LSB] = boost::uint8_t(rev_num >> 0); + bytes[DB_EEPROM_REV_MSB] = boost::uint8_t(rev_num >> 8); + } + + //load the checksum + bytes[DB_EEPROM_CHKSUM] = checksum(bytes); + + iface.write_eeprom(addr, 0, bytes); +} diff --git a/host/lib/usrp/dboard_id.cpp b/host/lib/usrp/dboard_id.cpp new file mode 100644 index 000000000..3028d2a3b --- /dev/null +++ b/host/lib/usrp/dboard_id.cpp @@ -0,0 +1,68 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_id.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <sstream> +#include <iostream> + +using namespace uhd::usrp; + +dboard_id_t::dboard_id_t(boost::uint16_t id){ + _id = id; +} + +dboard_id_t dboard_id_t::none(void){ + return dboard_id_t(); +} + +dboard_id_t dboard_id_t::from_uint16(boost::uint16_t uint16){ + return dboard_id_t(uint16); +} + +boost::uint16_t dboard_id_t::to_uint16(void) const{ + return _id; +} + +//used with lexical cast to parse a hex string +template <class T> struct to_hex{ + T value; + operator T() const {return value;} + friend std::istream& operator>>(std::istream& in, to_hex& out){ + in >> std::hex >> out.value; + return in; + } +}; + +dboard_id_t dboard_id_t::from_string(const std::string &string){ + if (string.substr(0, 2) == "0x"){ + return dboard_id_t::from_uint16(boost::lexical_cast<to_hex<boost::uint16_t> >(string)); + } + return dboard_id_t::from_uint16(boost::lexical_cast<boost::uint16_t>(string)); +} + +std::string dboard_id_t::to_string(void) const{ + return str(boost::format("0x%04x") % this->to_uint16()); +} + +//Note: to_pp_string is implemented in the dboard manager +//because it needs access to the dboard registration table + +bool uhd::usrp::operator==(const dboard_id_t &lhs, const dboard_id_t &rhs){ + return lhs.to_uint16() == rhs.to_uint16(); +} diff --git a/host/lib/usrp/dboard_iface.cpp b/host/lib/usrp/dboard_iface.cpp new file mode 100644 index 000000000..5cc5ea470 --- /dev/null +++ b/host/lib/usrp/dboard_iface.cpp @@ -0,0 +1,78 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> + +using namespace uhd::usrp; + +struct dboard_iface::impl{ + uhd::dict<unit_t, boost::uint16_t> pin_ctrl_shadow; + uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > atr_reg_shadow; + uhd::dict<unit_t, boost::uint16_t> gpio_ddr_shadow; + uhd::dict<unit_t, boost::uint16_t> gpio_out_shadow; +}; + +dboard_iface::dboard_iface(void){ + _impl = UHD_PIMPL_MAKE(impl, ()); +} + +template <typename T> +static T shadow_it(T &shadow, const T &value, const T &mask){ + shadow = (shadow & ~mask) | (value & mask); + return shadow; +} + +void dboard_iface::set_pin_ctrl( + unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ + _set_pin_ctrl(unit, shadow_it(_impl->pin_ctrl_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_pin_ctrl(unit_t unit){ + return _impl->pin_ctrl_shadow[unit]; +} + +void dboard_iface::set_atr_reg( + unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask +){ + _set_atr_reg(unit, reg, shadow_it(_impl->atr_reg_shadow[unit][reg], value, mask)); +} + +boost::uint16_t dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return _impl->atr_reg_shadow[unit][reg]; +} + +void dboard_iface::set_gpio_ddr( + unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ + _set_gpio_ddr(unit, shadow_it(_impl->gpio_ddr_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_gpio_ddr(unit_t unit){ + return _impl->gpio_ddr_shadow[unit]; +} + +void dboard_iface::set_gpio_out( + unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ + _set_gpio_out(unit, shadow_it(_impl->gpio_out_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_gpio_out(unit_t unit){ + return _impl->gpio_out_shadow[unit]; +} diff --git a/host/lib/usrp/dboard_manager.cpp b/host/lib/usrp/dboard_manager.cpp new file mode 100644 index 000000000..340c1d3f9 --- /dev/null +++ b/host/lib/usrp/dboard_manager.cpp @@ -0,0 +1,332 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dboard_ctor_args.hpp" +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/assign/list_of.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * dboard key class to use for look-up + **********************************************************************/ +class dboard_key_t{ +public: + dboard_key_t(const dboard_id_t &id = dboard_id_t::none()): + _rx_id(id), _tx_id(id), _xcvr(false){} + + dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id): + _rx_id(rx_id), _tx_id(tx_id), _xcvr(true){} + + dboard_id_t xx_id(void) const{ + UHD_ASSERT_THROW(not this->is_xcvr()); + return this->_rx_id; + } + + dboard_id_t rx_id(void) const{ + UHD_ASSERT_THROW(this->is_xcvr()); + return this->_rx_id; + } + + dboard_id_t tx_id(void) const{ + UHD_ASSERT_THROW(this->is_xcvr()); + return this->_tx_id; + } + + bool is_xcvr(void) const{ + return this->_xcvr; + } + +private: + dboard_id_t _rx_id, _tx_id; + bool _xcvr; +}; + +bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){ + if (lhs.is_xcvr() and rhs.is_xcvr()) + return lhs.rx_id() == rhs.rx_id() and lhs.tx_id() == rhs.tx_id(); + if (not lhs.is_xcvr() and not rhs.is_xcvr()) + return lhs.xx_id() == rhs.xx_id(); + return false; +} + +/*********************************************************************** + * storage and registering for dboards + **********************************************************************/ +//dboard registry tuple: dboard constructor, canonical name, subdev names +typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string> > args_t; + +//map a dboard id to a dboard constructor +typedef uhd::dict<dboard_key_t, args_t> id_to_args_map_t; +UHD_SINGLETON_FCN(id_to_args_map_t, get_id_to_args_map) + +static void register_dboard_key( + const dboard_key_t &dboard_key, + dboard_manager::dboard_ctor_t dboard_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names +){ + UHD_LOGV(always) << "registering: " << name << std::endl; + if (get_id_to_args_map().has_key(dboard_key)){ + + if (dboard_key.is_xcvr()) throw uhd::key_error(str(boost::format( + "The dboard id pair [%s, %s] is already registered to %s." + ) % dboard_key.rx_id().to_string() % dboard_key.tx_id().to_string() % get_id_to_args_map()[dboard_key].get<1>())); + + else throw uhd::key_error(str(boost::format( + "The dboard id %s is already registered to %s." + ) % dboard_key.xx_id().to_string() % get_id_to_args_map()[dboard_key].get<1>())); + + } + get_id_to_args_map()[dboard_key] = args_t(dboard_ctor, name, subdev_names); +} + +void dboard_manager::register_dboard( + const dboard_id_t &dboard_id, + dboard_ctor_t dboard_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names +){ + register_dboard_key(dboard_key_t(dboard_id), dboard_ctor, name, subdev_names); +} + +void dboard_manager::register_dboard( + const dboard_id_t &rx_dboard_id, + const dboard_id_t &tx_dboard_id, + dboard_ctor_t dboard_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names +){ + register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), dboard_ctor, name, subdev_names); +} + +std::string dboard_id_t::to_cname(void) const{ + std::string cname; + BOOST_FOREACH(const dboard_key_t &key, get_id_to_args_map().keys()){ + if ( + (not key.is_xcvr() and *this == key.xx_id()) or + (key.is_xcvr() and (*this == key.rx_id() or *this == key.tx_id())) + ){ + if (not cname.empty()) cname += ", "; + cname += get_id_to_args_map()[key].get<1>(); + } + } + return (cname.empty())? "Unknown" : cname; +} + +std::string dboard_id_t::to_pp_string(void) const{ + return str(boost::format("%s (%s)") % this->to_cname() % this->to_string()); +} + +/*********************************************************************** + * dboard manager implementation class + **********************************************************************/ +class dboard_manager_impl : public dboard_manager{ + +public: + dboard_manager_impl( + dboard_id_t rx_dboard_id, + dboard_id_t tx_dboard_id, + dboard_iface::sptr iface, + property_tree::sptr subtree + ); + ~dboard_manager_impl(void); + +private: + void init(dboard_id_t, dboard_id_t, property_tree::sptr); + //list of rx and tx dboards in this dboard_manager + //each dboard here is actually a subdevice proxy + //the subdevice proxy is internal to the cpp file + uhd::dict<std::string, dboard_base::sptr> _rx_dboards; + uhd::dict<std::string, dboard_base::sptr> _tx_dboards; + dboard_iface::sptr _iface; + void set_nice_dboard_if(void); +}; + +/*********************************************************************** + * make routine for dboard manager + **********************************************************************/ +dboard_manager::sptr dboard_manager::make( + dboard_id_t rx_dboard_id, + dboard_id_t tx_dboard_id, + dboard_id_t gdboard_id, + dboard_iface::sptr iface, + property_tree::sptr subtree +){ + return dboard_manager::sptr( + new dboard_manager_impl( + rx_dboard_id, + (gdboard_id == dboard_id_t::none())? tx_dboard_id : gdboard_id, + iface, subtree + ) + ); +} + +/*********************************************************************** + * implementation class methods + **********************************************************************/ +dboard_manager_impl::dboard_manager_impl( + dboard_id_t rx_dboard_id, + dboard_id_t tx_dboard_id, + dboard_iface::sptr iface, + property_tree::sptr subtree +): + _iface(iface) +{ + try{ + this->init(rx_dboard_id, tx_dboard_id, subtree); + } + catch(const std::exception &e){ + UHD_MSG(error) << boost::format( + "The daughterboard manager encountered a recoverable error in init.\n" + "Loading the \"unknown\" daughterboard implementations to continue.\n" + "The daughterboard cannot operate until this error is resolved.\n" + ) << e.what() << std::endl; + //clean up the stuff added by the call above + if (subtree->exists("rx_frontends")) subtree->remove("rx_frontends"); + if (subtree->exists("tx_frontends")) subtree->remove("tx_frontends"); + this->init(dboard_id_t::none(), dboard_id_t::none(), subtree); + } +} + +void dboard_manager_impl::init( + dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, property_tree::sptr subtree +){ + //find the dboard key matches for the dboard ids + dboard_key_t rx_dboard_key, tx_dboard_key, xcvr_dboard_key; + BOOST_FOREACH(const dboard_key_t &key, get_id_to_args_map().keys()){ + if (key.is_xcvr()){ + if (rx_dboard_id == key.rx_id() and tx_dboard_id == key.tx_id()) xcvr_dboard_key = key; + if (rx_dboard_id == key.rx_id()) rx_dboard_key = key; //kept to handle warning + if (tx_dboard_id == key.tx_id()) tx_dboard_key = key; //kept to handle warning + } + else{ + if (rx_dboard_id == key.xx_id()) rx_dboard_key = key; + if (tx_dboard_id == key.xx_id()) tx_dboard_key = key; + } + } + + //warn for invalid dboard id xcvr combinations + if (not xcvr_dboard_key.is_xcvr() and (rx_dboard_key.is_xcvr() or tx_dboard_key.is_xcvr())){ + UHD_MSG(warning) << boost::format( + "Unknown transceiver board ID combination.\n" + "Is your daughter-board mounted properly?\n" + "RX dboard ID: %s\n" + "TX dboard ID: %s\n" + ) % rx_dboard_id.to_pp_string() % tx_dboard_id.to_pp_string(); + } + + //initialize the gpio pins before creating subdevs + set_nice_dboard_if(); + + //dboard constructor args + dboard_ctor_args_t db_ctor_args; + db_ctor_args.db_iface = _iface; + + //make xcvr subdevs + if (xcvr_dboard_key.is_xcvr()){ + + //extract data for the xcvr dboard key + dboard_ctor_t dboard_ctor; std::string name; std::vector<std::string> subdevs; + boost::tie(dboard_ctor, name, subdevs) = get_id_to_args_map()[xcvr_dboard_key]; + + //create the xcvr object for each subdevice + BOOST_FOREACH(const std::string &subdev, subdevs){ + db_ctor_args.sd_name = subdev; + db_ctor_args.rx_id = rx_dboard_id; + db_ctor_args.tx_id = tx_dboard_id; + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev); + dboard_base::sptr xcvr_dboard = dboard_ctor(&db_ctor_args); + _rx_dboards[subdev] = xcvr_dboard; + _tx_dboards[subdev] = xcvr_dboard; + } + } + + //make tx and rx subdevs (separate subdevs for rx and tx dboards) + else{ + + //force the rx key to the unknown board for bad combinations + if (rx_dboard_key.is_xcvr() or rx_dboard_key.xx_id() == dboard_id_t::none()){ + rx_dboard_key = dboard_key_t(0xfff1); + } + + //extract data for the rx dboard key + dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs; + boost::tie(rx_dboard_ctor, rx_name, rx_subdevs) = get_id_to_args_map()[rx_dboard_key]; + + //make the rx subdevs + BOOST_FOREACH(const std::string &subdev, rx_subdevs){ + db_ctor_args.sd_name = subdev; + db_ctor_args.rx_id = rx_dboard_id; + db_ctor_args.tx_id = dboard_id_t::none(); + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev); + db_ctor_args.tx_subtree = property_tree::sptr(); //null + _rx_dboards[subdev] = rx_dboard_ctor(&db_ctor_args); + } + + //force the tx key to the unknown board for bad combinations + if (tx_dboard_key.is_xcvr() or tx_dboard_key.xx_id() == dboard_id_t::none()){ + tx_dboard_key = dboard_key_t(0xfff0); + } + + //extract data for the tx dboard key + dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs; + boost::tie(tx_dboard_ctor, tx_name, tx_subdevs) = get_id_to_args_map()[tx_dboard_key]; + + //make the tx subdevs + BOOST_FOREACH(const std::string &subdev, tx_subdevs){ + db_ctor_args.sd_name = subdev; + db_ctor_args.rx_id = dboard_id_t::none(); + db_ctor_args.tx_id = tx_dboard_id; + db_ctor_args.rx_subtree = property_tree::sptr(); //null + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev); + _tx_dboards[subdev] = tx_dboard_ctor(&db_ctor_args); + } + } +} + +dboard_manager_impl::~dboard_manager_impl(void){UHD_SAFE_CALL( + set_nice_dboard_if(); +)} + +void dboard_manager_impl::set_nice_dboard_if(void){ + //make a list of possible unit types + std::vector<dboard_iface::unit_t> units = boost::assign::list_of + (dboard_iface::UNIT_RX) + (dboard_iface::UNIT_TX) + ; + + //set nice settings on each unit + BOOST_FOREACH(dboard_iface::unit_t unit, units){ + _iface->set_gpio_ddr(unit, 0x0000); //all inputs + _iface->set_gpio_out(unit, 0x0000); //all low + _iface->set_pin_ctrl(unit, 0x0000); //all gpio + _iface->set_clock_enabled(unit, false); //clock off + } +} diff --git a/host/lib/usrp/e100/CMakeLists.txt b/host/lib/usrp/e100/CMakeLists.txt new file mode 100644 index 000000000..ac9d8c655 --- /dev/null +++ b/host/lib/usrp/e100/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP-E100 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF) + +IF(ENABLE_E100) + INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include) + + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e100_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e100_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e100_mmap_zero_copy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fpga_downloader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp + ) +ENDIF(ENABLE_E100) diff --git a/host/lib/usrp/e100/clock_ctrl.cpp b/host/lib/usrp/e100/clock_ctrl.cpp new file mode 100644 index 000000000..9e355ce17 --- /dev/null +++ b/host/lib/usrp/e100/clock_ctrl.cpp @@ -0,0 +1,538 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9522_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/cstdint.hpp> +#include "e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <algorithm> +#include <utility> + +using namespace uhd; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const bool ENABLE_THE_TEST_OUT = true; +static const double REFERENCE_INPUT_RATE = 10e6; + +/*********************************************************************** + * Helpers + **********************************************************************/ +template <typename div_type, typename bypass_type> static void set_clock_divider( + size_t divider, div_type &low, div_type &high, bypass_type &bypass +){ + high = divider/2 - 1; + low = divider - high - 2; + bypass = (divider == 1)? 1 : 0; +} + +/*********************************************************************** + * Clock rate calculation stuff: + * Using the internal VCO between 1400 and 1800 MHz + **********************************************************************/ +struct clock_settings_type{ + size_t ref_clock_doubler, r_counter, a_counter, b_counter, prescaler, vco_divider, chan_divider; + size_t get_n_counter(void) const{return prescaler * b_counter + a_counter;} + double get_ref_rate(void) const{return REFERENCE_INPUT_RATE * ref_clock_doubler;} + double get_vco_rate(void) const{return get_ref_rate()/r_counter * get_n_counter();} + double get_chan_rate(void) const{return get_vco_rate()/vco_divider;} + double get_out_rate(void) const{return get_chan_rate()/chan_divider;} + std::string to_pp_string(void) const{ + return str(boost::format( + " r_counter: %d\n" + " a_counter: %d\n" + " b_counter: %d\n" + " prescaler: %d\n" + " vco_divider: %d\n" + " chan_divider: %d\n" + " vco_rate: %fMHz\n" + " chan_rate: %fMHz\n" + " out_rate: %fMHz\n" + ) + % r_counter + % a_counter + % b_counter + % prescaler + % vco_divider + % chan_divider + % (get_vco_rate()/1e6) + % (get_chan_rate()/1e6) + % (get_out_rate()/1e6) + ); + } +}; + +//! gives the greatest divisor of num between 1 and max inclusive +template<typename T> static inline T greatest_divisor(T num, T max){ + for (T i = max; i > 1; i--) if (num%i == 0) return i; return 1; +} + +//! gives the least divisor of num between min and num exclusive +template<typename T> static inline T least_divisor(T num, T min){ + for (T i = min; i < num; i++) if (num%i == 0) return i; return 1; +} + +static clock_settings_type get_clock_settings(double rate){ + clock_settings_type cs; + cs.ref_clock_doubler = 2; //always doubling + cs.prescaler = 8; //set to 8 when input is under 2400 MHz + + //basic formulas used below: + //out_rate*X = ref_rate*Y + //X = i*ref_rate/gcd + //Y = i*out_rate/gcd + //X = chan_div * vco_div * R + //Y = P*B + A + + const boost::uint64_t out_rate = boost::uint64_t(rate); + const boost::uint64_t ref_rate = boost::uint64_t(cs.get_ref_rate()); + const size_t gcd = size_t(boost::math::gcd(ref_rate, out_rate)); + + for (size_t i = 1; i <= 100; i++){ + const size_t X = i*ref_rate/gcd; + const size_t Y = i*out_rate/gcd; + + //determine A and B (P is fixed) + cs.b_counter = Y/cs.prescaler; + cs.a_counter = Y - cs.b_counter*cs.prescaler; + + static const double vco_bound_pad = 100e6; + for ( //calculate an r divider that fits into the bounds of the vco + cs.r_counter = size_t(cs.get_n_counter()*cs.get_ref_rate()/(1800e6 - vco_bound_pad)); + cs.r_counter <= size_t(cs.get_n_counter()*cs.get_ref_rate()/(1400e6 + vco_bound_pad)) + and cs.r_counter > 0; cs.r_counter++ + ){ + + //determine chan_div and vco_div + //and fill in that order of preference + cs.chan_divider = greatest_divisor<size_t>(X/cs.r_counter, 32); + cs.vco_divider = greatest_divisor<size_t>(X/cs.chan_divider/cs.r_counter, 6); + + //avoid a vco divider of 1 (if possible) + if (cs.vco_divider == 1){ + cs.vco_divider = least_divisor<size_t>(cs.chan_divider, 2); + cs.chan_divider /= cs.vco_divider; + } + + UHD_LOGV(always) + << "gcd " << gcd << std::endl + << "X " << X << std::endl + << "Y " << Y << std::endl + << cs.to_pp_string() << std::endl + ; + + //filter limits on the counters + if (cs.vco_divider == 1) continue; + if (cs.r_counter >= (1<<14)) continue; + if (cs.b_counter == 2) continue; + if (cs.b_counter == 1 and cs.a_counter != 0) continue; + if (cs.b_counter >= (1<<13)) continue; + if (cs.a_counter >= (1<<6)) continue; + if (cs.get_vco_rate() > 1800e6 - vco_bound_pad) continue; + if (cs.get_vco_rate() < 1400e6 + vco_bound_pad) continue; + if (cs.get_out_rate() != rate) continue; + + UHD_MSG(status) << "USRP-E100 clock control: " << i << std::endl << cs.to_pp_string() << std::endl; + return cs; + } + } + + throw uhd::value_error(str(boost::format( + "USRP-E100 clock control: could not calculate settings for clock rate %fMHz" + ) % (rate/1e6))); +} + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class e100_clock_ctrl_impl : public e100_clock_ctrl{ +public: + e100_clock_ctrl_impl(spi_iface::sptr iface, double master_clock_rate, const bool dboard_clocks_diff): + _dboard_clocks_diff(dboard_clocks_diff) + { + _iface = iface; + _chan_rate = 0.0; + _out_rate = 0.0; + + //perform soft-reset + _ad9522_regs.soft_reset = 1; + this->send_reg(0x000); + this->latch_regs(); + _ad9522_regs.soft_reset = 0; + + //init the clock gen registers + //Note: out0 should already be clocking the FPGA or this isnt going to work + _ad9522_regs.sdo_active = ad9522_regs_t::SDO_ACTIVE_SDO_SDIO; + _ad9522_regs.enb_stat_eeprom_at_stat_pin = 0; //use status pin + _ad9522_regs.status_pin_control = 0x1; //n divider + _ad9522_regs.ld_pin_control = 0x00; //dld + _ad9522_regs.refmon_pin_control = 0x12; //show ref2 + _ad9522_regs.lock_detect_counter = ad9522_regs_t::LOCK_DETECT_COUNTER_16CYC; + + this->use_internal_ref(); + + //initialize the FPGA clock rate + UHD_MSG(status) << boost::format("Initializing FPGA clock to %fMHz...") % (master_clock_rate/1e6) << std::endl; + this->set_fpga_clock_rate(master_clock_rate); + + this->enable_test_clock(ENABLE_THE_TEST_OUT); + this->enable_rx_dboard_clock(false); + this->enable_tx_dboard_clock(false); + } + + ~e100_clock_ctrl_impl(void){ + this->enable_test_clock(ENABLE_THE_TEST_OUT); + this->enable_rx_dboard_clock(false); + this->enable_tx_dboard_clock(false); + } + + /*********************************************************************** + * Clock rate control: + * - set clock rate w/ internal VCO + * - set clock rate w/ external VCXO + **********************************************************************/ + void set_clock_settings_with_internal_vco(double rate){ + const clock_settings_type cs = get_clock_settings(rate); + + //set the rates to private variables so the implementation knows! + _chan_rate = cs.get_chan_rate(); + _out_rate = cs.get_out_rate(); + + _ad9522_regs.enable_clock_doubler = (cs.ref_clock_doubler == 2)? 1 : 0; + + _ad9522_regs.set_r_counter(cs.r_counter); + _ad9522_regs.a_counter = cs.a_counter; + _ad9522_regs.set_b_counter(cs.b_counter); + UHD_ASSERT_THROW(cs.prescaler == 8); //assumes this below: + _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV8_9; + + _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; + _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + + _ad9522_regs.bypass_vco_divider = 0; + switch(cs.vco_divider){ + case 1: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV1; break; + case 2: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV2; break; + case 3: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV3; break; + case 4: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV4; break; + case 5: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV5; break; + case 6: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV6; break; + } + _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_VCO; + + //setup fpga master clock + _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; + set_clock_divider(cs.chan_divider, + _ad9522_regs.divider0_low_cycles, + _ad9522_regs.divider0_high_cycles, + _ad9522_regs.divider0_bypass + ); + + //setup codec clock + _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; + set_clock_divider(cs.chan_divider, + _ad9522_regs.divider1_low_cycles, + _ad9522_regs.divider1_high_cycles, + _ad9522_regs.divider1_bypass + ); + + this->send_all_regs(); + calibrate_now(); + } + + void set_clock_settings_with_external_vcxo(double rate){ + //set the rates to private variables so the implementation knows! + _chan_rate = rate; + _out_rate = rate; + + _ad9522_regs.enable_clock_doubler = 1; //doubler always on + const double ref_rate = REFERENCE_INPUT_RATE*2; + + //bypass prescaler such that N = B + long gcd = boost::math::gcd(long(ref_rate), long(rate)); + _ad9522_regs.set_r_counter(int(ref_rate/gcd)); + _ad9522_regs.a_counter = 0; + _ad9522_regs.set_b_counter(int(rate/gcd)); + _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV1; + + //setup external vcxo + _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; + _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + _ad9522_regs.bypass_vco_divider = 1; + _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_EXTERNAL; + + //setup fpga master clock + _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; + _ad9522_regs.divider0_bypass = 1; + + //setup codec clock + _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; + _ad9522_regs.divider1_bypass = 1; + + this->send_all_regs(); + } + + void set_fpga_clock_rate(double rate){ + if (_out_rate == rate) return; + if (rate == 61.44e6) set_clock_settings_with_external_vcxo(rate); + else set_clock_settings_with_internal_vco(rate); + set_rx_dboard_clock_rate(rate); + set_tx_dboard_clock_rate(rate); + } + + double get_fpga_clock_rate(void){ + return this->_out_rate; + } + + /*********************************************************************** + * Special test clock output + **********************************************************************/ + void enable_test_clock(bool enb){ + //setup test clock (same divider as codec clock) + _ad9522_regs.out4_format = ad9522_regs_t::OUT4_FORMAT_CMOS; + _ad9522_regs.out4_cmos_configuration = (enb)? + ad9522_regs_t::OUT4_CMOS_CONFIGURATION_A_ON : + ad9522_regs_t::OUT4_CMOS_CONFIGURATION_OFF; + this->send_reg(0x0F4); + this->latch_regs(); + } + + /*********************************************************************** + * RX Dboard Clock Control (output 9, divider 3) + **********************************************************************/ + void enable_rx_dboard_clock(bool enb){ + if (_dboard_clocks_diff){ + _ad9522_regs.out9_format = ad9522_regs_t::OUT9_FORMAT_LVDS; + _ad9522_regs.out9_lvds_power_down = enb? 0 : 1; + } + else{ + _ad9522_regs.out9_format = ad9522_regs_t::OUT9_FORMAT_CMOS; + _ad9522_regs.out9_cmos_configuration = (enb)? + ad9522_regs_t::OUT9_CMOS_CONFIGURATION_B_ON : + ad9522_regs_t::OUT9_CMOS_CONFIGURATION_OFF; + } + this->send_reg(0x0F9); + this->latch_regs(); + } + + std::vector<double> get_rx_dboard_clock_rates(void){ + std::vector<double> rates; + for(size_t div = 1; div <= 16+16; div++) + rates.push_back(this->_chan_rate/div); + return rates; + } + + void set_rx_dboard_clock_rate(double rate){ + assert_has(get_rx_dboard_clock_rates(), rate, "rx dboard clock rate"); + _rx_clock_rate = rate; + size_t divider = size_t(this->_chan_rate/rate); + //set the divider registers + set_clock_divider(divider, + _ad9522_regs.divider3_low_cycles, + _ad9522_regs.divider3_high_cycles, + _ad9522_regs.divider3_bypass + ); + this->send_reg(0x199); + this->send_reg(0x19a); + this->soft_sync(); + } + + double get_rx_clock_rate(void){ + return _rx_clock_rate; + } + + /*********************************************************************** + * TX Dboard Clock Control (output 6, divider 2) + **********************************************************************/ + void enable_tx_dboard_clock(bool enb){ + if (_dboard_clocks_diff){ + _ad9522_regs.out6_format = ad9522_regs_t::OUT6_FORMAT_LVDS; + _ad9522_regs.out6_lvds_power_down = enb? 0 : 1; + } + else{ + _ad9522_regs.out6_format = ad9522_regs_t::OUT6_FORMAT_CMOS; + _ad9522_regs.out6_cmos_configuration = (enb)? + ad9522_regs_t::OUT6_CMOS_CONFIGURATION_B_ON : + ad9522_regs_t::OUT6_CMOS_CONFIGURATION_OFF; + } + this->send_reg(0x0F6); + this->latch_regs(); + } + + std::vector<double> get_tx_dboard_clock_rates(void){ + return get_rx_dboard_clock_rates(); //same master clock, same dividers... + } + + void set_tx_dboard_clock_rate(double rate){ + assert_has(get_tx_dboard_clock_rates(), rate, "tx dboard clock rate"); + _tx_clock_rate = rate; + size_t divider = size_t(this->_chan_rate/rate); + //set the divider registers + set_clock_divider(divider, + _ad9522_regs.divider2_low_cycles, + _ad9522_regs.divider2_high_cycles, + _ad9522_regs.divider2_bypass + ); + this->send_reg(0x196); + this->send_reg(0x197); + this->soft_sync(); + } + + double get_tx_clock_rate(void){ + return _tx_clock_rate; + } + + /*********************************************************************** + * Clock reference control + **********************************************************************/ + void use_internal_ref(void) { + _ad9522_regs.enable_ref2 = 1; + _ad9522_regs.enable_ref1 = 0; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF2; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; + this->send_reg(0x01C); + this->latch_regs(); + } + + void use_external_ref(void) { + _ad9522_regs.enable_ref2 = 0; + _ad9522_regs.enable_ref1 = 1; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; + this->send_reg(0x01C); + this->latch_regs(); + } + + void use_auto_ref(void) { + _ad9522_regs.enable_ref2 = 1; + _ad9522_regs.enable_ref1 = 1; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_AUTO; + this->send_reg(0x01C); + this->latch_regs(); + } + + bool get_locked(void){ + static const boost::uint8_t addr = 0x01F; + boost::uint32_t reg = _iface->read_spi( + UE_SPI_SS_AD9522, spi_config_t::EDGE_RISE, + _ad9522_regs.get_read_reg(addr), 24 + ); + _ad9522_regs.set_reg(addr, reg); + return _ad9522_regs.digital_lock_detect != 0; + } + +private: + spi_iface::sptr _iface; + const bool _dboard_clocks_diff; + ad9522_regs_t _ad9522_regs; + double _out_rate; //rate at the fpga and codec + double _chan_rate; //rate before final dividers + double _rx_clock_rate, _tx_clock_rate; + + void latch_regs(void){ + _ad9522_regs.io_update = 1; + this->send_reg(0x232); + } + + void send_reg(boost::uint16_t addr){ + boost::uint32_t reg = _ad9522_regs.get_write_reg(addr); + UHD_LOGV(often) << "clock control write reg: " << std::hex << reg << std::endl; + _iface->write_spi( + UE_SPI_SS_AD9522, + spi_config_t::EDGE_RISE, + reg, 24 + ); + } + + void calibrate_now(void){ + //vco calibration routine: + _ad9522_regs.vco_calibration_now = 0; + this->send_reg(0x18); + this->latch_regs(); + _ad9522_regs.vco_calibration_now = 1; + this->send_reg(0x18); + this->latch_regs(); + //wait for calibration done: + static const boost::uint8_t addr = 0x01F; + for (size_t ms10 = 0; ms10 < 100; ms10++){ + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + boost::uint32_t reg = _iface->read_spi( + UE_SPI_SS_AD9522, spi_config_t::EDGE_RISE, + _ad9522_regs.get_read_reg(addr), 24 + ); + _ad9522_regs.set_reg(addr, reg); + if (_ad9522_regs.vco_calibration_finished) goto wait_for_ld; + } + UHD_MSG(error) << "USRP-E100 clock control: VCO calibration timeout" << std::endl; + wait_for_ld: + //wait for digital lock detect: + for (size_t ms10 = 0; ms10 < 100; ms10++){ + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + boost::uint32_t reg = _iface->read_spi( + UE_SPI_SS_AD9522, spi_config_t::EDGE_RISE, + _ad9522_regs.get_read_reg(addr), 24 + ); + _ad9522_regs.set_reg(addr, reg); + if (_ad9522_regs.digital_lock_detect) return; + } + UHD_MSG(error) << "USRP-E100 clock control: lock detection timeout" << std::endl; + } + + void soft_sync(void){ + _ad9522_regs.soft_sync = 1; + this->send_reg(0x230); + this->latch_regs(); + _ad9522_regs.soft_sync = 0; + this->send_reg(0x230); + this->latch_regs(); + } + + void send_all_regs(void){ + //setup a list of register ranges to write + typedef std::pair<boost::uint16_t, boost::uint16_t> range_t; + static const std::vector<range_t> ranges = boost::assign::list_of + (range_t(0x000, 0x000)) (range_t(0x010, 0x01F)) + (range_t(0x0F0, 0x0FD)) (range_t(0x190, 0x19B)) + (range_t(0x1E0, 0x1E1)) (range_t(0x230, 0x230)) + ; + + //write initial register values and latch/update + BOOST_FOREACH(const range_t &range, ranges){ + for(boost::uint16_t addr = range.first; addr <= range.second; addr++){ + this->send_reg(addr); + } + } + this->latch_regs(); + } +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +e100_clock_ctrl::sptr e100_clock_ctrl::make(spi_iface::sptr iface, double master_clock_rate, const bool dboard_clocks_diff){ + return sptr(new e100_clock_ctrl_impl(iface, master_clock_rate, dboard_clocks_diff)); +} diff --git a/host/lib/usrp/e100/clock_ctrl.hpp b/host/lib/usrp/e100/clock_ctrl.hpp new file mode 100644 index 000000000..803265556 --- /dev/null +++ b/host/lib/usrp/e100/clock_ctrl.hpp @@ -0,0 +1,127 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_CLOCK_CTRL_HPP +#define INCLUDED_USRP_E100_CLOCK_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp-e clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class e100_clock_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<e100_clock_ctrl> sptr; + + /*! + * Make a new clock control object. + * \param iface the spi iface object + * \param master clock rate the FPGA rate + * param dboard_clocks_diff are they differential? + * \return the clock control object + */ + static sptr make(uhd::spi_iface::sptr iface, double master_clock_rate, const bool dboard_clocks_diff); + + /*! + * Set the rate of the fpga clock line. + * Throws if rate is not valid. + * \param rate the new rate in Hz + */ + virtual void set_fpga_clock_rate(double rate) = 0; + + /*! + * Get the rate of the fpga clock line. + * \return the fpga clock rate in Hz + */ + virtual double get_fpga_clock_rate(void) = 0; + + /*! + * Get the possible rates of the rx dboard clock. + * \return a vector of clock rates in Hz + */ + virtual std::vector<double> get_rx_dboard_clock_rates(void) = 0; + + /*! + * Get the possible rates of the tx dboard clock. + * \return a vector of clock rates in Hz + */ + virtual std::vector<double> get_tx_dboard_clock_rates(void) = 0; + + /*! + * Set the rx dboard clock rate to a possible rate. + * \param rate the new clock rate in Hz + * \throw exception when rate cannot be achieved + */ + virtual void set_rx_dboard_clock_rate(double rate) = 0; + + /*! + * Set the tx dboard clock rate to a possible rate. + * \param rate the new clock rate in Hz + * \throw exception when rate cannot be achieved + */ + virtual void set_tx_dboard_clock_rate(double rate) = 0; + + /*! + * Get the current rx dboard clock rate. + * \return the clock rate in Hz + */ + virtual double get_rx_clock_rate(void) = 0; + + /*! + * Get the current tx dboard clock rate. + * \return the clock rate in Hz + */ + virtual double get_tx_clock_rate(void) = 0; + + /*! + * Enable/disable the rx dboard clock. + * \param enb true to enable + */ + virtual void enable_rx_dboard_clock(bool enb) = 0; + + /*! + * Enable/disable the tx dboard clock. + * \param enb true to enable + */ + virtual void enable_tx_dboard_clock(bool enb) = 0; + + /*! + * Use the internal TCXO reference + */ + virtual void use_internal_ref(void) = 0; + + /*! + * Use the external SMA reference + */ + virtual void use_external_ref(void) = 0; + + /*! + * Use external if available, internal otherwise + */ + virtual void use_auto_ref(void) = 0; + + //! Is the reference locked? + virtual bool get_locked(void) = 0; + +}; + +#endif /* INCLUDED_USRP_E100_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/e100/codec_ctrl.cpp b/host/lib/usrp/e100/codec_ctrl.cpp new file mode 100644 index 000000000..13b3bc951 --- /dev/null +++ b/host/lib/usrp/e100/codec_ctrl.cpp @@ -0,0 +1,288 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/cstdint.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include "e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> + +using namespace uhd; + +const gain_range_t e100_codec_ctrl::tx_pga_gain_range(-20, 0, double(0.1)); +const gain_range_t e100_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class e100_codec_ctrl_impl : public e100_codec_ctrl{ +public: + //structors + e100_codec_ctrl_impl(spi_iface::sptr iface); + ~e100_codec_ctrl_impl(void); + + //aux adc and dac control + double read_aux_adc(aux_adc_t which); + void write_aux_dac(aux_dac_t which, double volts); + + //pga gain control + void set_tx_pga_gain(double); + double get_tx_pga_gain(void); + void set_rx_pga_gain(double, char); + double get_rx_pga_gain(char); + +private: + spi_iface::sptr _iface; + ad9862_regs_t _ad9862_regs; + void send_reg(boost::uint8_t addr); + void recv_reg(boost::uint8_t addr); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +e100_codec_ctrl_impl::e100_codec_ctrl_impl(spi_iface::sptr iface){ + _iface = iface; + + //soft reset + _ad9862_regs.soft_reset = 1; + this->send_reg(0); + + //initialize the codec register settings + _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; + _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; + _ad9862_regs.soft_reset = 0; + + //setup rx side of codec + _ad9862_regs.byp_buffer_a = 0; + _ad9862_regs.byp_buffer_b = 0; + _ad9862_regs.buffer_a_pd = 0; + _ad9862_regs.buffer_b_pd = 0; + _ad9862_regs.rx_pga_a = 0;//0x1f; //TODO bring under api control + _ad9862_regs.rx_pga_b = 0;//0x1f; //TODO bring under api control + _ad9862_regs.rx_twos_comp = 1; + _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; + _ad9862_regs.shared_ref = 1; + + //setup tx side of codec + _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; + _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; + _ad9862_regs.tx_retime = ad9862_regs_t::TX_RETIME_CLKOUT2; + _ad9862_regs.tx_pga_gain = 199; //TODO bring under api control + _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; + _ad9862_regs.interp = ad9862_regs_t::INTERP_2; + _ad9862_regs.tx_twos_comp = 1; + _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_BYPASS; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; + _ad9862_regs.dac_a_coarse_gain = 0x3; + _ad9862_regs.dac_b_coarse_gain = 0x3; + _ad9862_regs.edges = ad9862_regs_t::EDGES_NORMAL; + + //setup the dll + _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; + _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; + _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; + _ad9862_regs.hs_duty_cycle = 1; + _ad9862_regs.clk_duty = 1; + + //disable clkout1 and clkout2 + _ad9862_regs.dis1 = ad9862_regs_t::DIS1_DIS; + //_ad9862_regs.dis2 = ad9862_regs_t::DIS2_DIS; needed for transmit + + //write the register settings to the codec + for (uint8_t addr = 0; addr <= 25; addr++){ + this->send_reg(addr); + } + + //always start conversions for aux ADC + _ad9862_regs.start_a = 1; + _ad9862_regs.start_b = 1; + + //aux adc clock + _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; + this->send_reg(34); + this->send_reg(35); +} + +e100_codec_ctrl_impl::~e100_codec_ctrl_impl(void){ + //set aux dacs to zero + this->write_aux_dac(AUX_DAC_A, 0); + this->write_aux_dac(AUX_DAC_B, 0); + this->write_aux_dac(AUX_DAC_C, 0); + this->write_aux_dac(AUX_DAC_D, 0); + + //power down + _ad9862_regs.all_rx_pd = 1; + this->send_reg(1); + _ad9862_regs.tx_digital_pd = 1; + _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; + this->send_reg(8); +} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void e100_codec_ctrl_impl::set_tx_pga_gain(double gain){ + int gain_word = int(mtpgw*(gain - tx_pga_gain_range.start())/(tx_pga_gain_range.stop() - tx_pga_gain_range.start())); + _ad9862_regs.tx_pga_gain = uhd::clip(gain_word, 0, mtpgw); + this->send_reg(16); +} + +double e100_codec_ctrl_impl::get_tx_pga_gain(void){ + return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.stop() - tx_pga_gain_range.start())/mtpgw) + tx_pga_gain_range.start(); +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void e100_codec_ctrl_impl::set_rx_pga_gain(double gain, char which){ + int gain_word = int(mrpgw*(gain - rx_pga_gain_range.start())/(rx_pga_gain_range.stop() - rx_pga_gain_range.start())); + gain_word = uhd::clip(gain_word, 0, mrpgw); + switch(which){ + case 'A': + _ad9862_regs.rx_pga_a = gain_word; + this->send_reg(2); + return; + case 'B': + _ad9862_regs.rx_pga_b = gain_word; + this->send_reg(3); + return; + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +double e100_codec_ctrl_impl::get_rx_pga_gain(char which){ + int gain_word; + switch(which){ + case 'A': gain_word = _ad9862_regs.rx_pga_a; break; + case 'B': gain_word = _ad9862_regs.rx_pga_b; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + return (gain_word*(rx_pga_gain_range.stop() - rx_pga_gain_range.start())/mrpgw) + rx_pga_gain_range.start(); +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static double aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low){ + return double((boost::uint16_t(high) << 2) | low)*3.3/0x3ff; +} + +double e100_codec_ctrl_impl::read_aux_adc(aux_adc_t which){ + switch(which){ + case AUX_ADC_A1: + _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC1; + this->send_reg(34); //start conversion and select mux + this->recv_reg(28); //read the value (2 bytes, 2 reads) + this->recv_reg(29); + return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); + + case AUX_ADC_A2: + _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC2; + this->send_reg(34); //start conversion and select mux + this->recv_reg(26); //read the value (2 bytes, 2 reads) + this->recv_reg(27); + return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); + + case AUX_ADC_B1: + _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC1; + this->send_reg(34); //start conversion and select mux + this->recv_reg(32); //read the value (2 bytes, 2 reads) + this->recv_reg(33); + return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); + + case AUX_ADC_B2: + _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC2; + this->send_reg(34); //start conversion and select mux + this->recv_reg(30); //read the value (2 bytes, 2 reads) + this->recv_reg(31); + return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void e100_codec_ctrl_impl::write_aux_dac(aux_dac_t which, double volts){ + //special case for aux dac d (aka sigma delta word) + if (which == AUX_DAC_D){ + boost::uint16_t dac_word = uhd::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); + _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); + _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); + this->send_reg(42); + this->send_reg(43); + return; + } + + //calculate the dac word for aux dac a, b, c + boost::uint8_t dac_word = uhd::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + + //setup a lookup table for the aux dac params (reg ref, reg addr) + typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; + uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of + (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) + (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) + (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) + ; + + //set the aux dac register + UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); + boost::uint8_t *reg_ref, reg_addr; + boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; + *reg_ref = dac_word; + this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void e100_codec_ctrl_impl::send_reg(boost::uint8_t addr){ + boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); + UHD_LOGV(often) << "codec control write reg: " << std::hex << reg << std::endl; + _iface->write_spi( + UE_SPI_SS_AD9862, + spi_config_t::EDGE_RISE, + reg, 16 + ); +} + +void e100_codec_ctrl_impl::recv_reg(boost::uint8_t addr){ + boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); + UHD_LOGV(often) << "codec control read reg: " << std::hex << reg << std::endl; + boost::uint32_t ret = _iface->read_spi( + UE_SPI_SS_AD9862, + spi_config_t::EDGE_RISE, + reg, 16 + ); + UHD_LOGV(often) << "codec control read ret: " << std::hex << ret << std::endl; + _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +e100_codec_ctrl::sptr e100_codec_ctrl::make(spi_iface::sptr iface){ + return sptr(new e100_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/e100/codec_ctrl.hpp b/host/lib/usrp/e100/codec_ctrl.hpp new file mode 100644 index 000000000..707f6f521 --- /dev/null +++ b/host/lib/usrp/e100/codec_ctrl.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_CODEC_CTRL_HPP +#define INCLUDED_USRP_E100_CODEC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp-e codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class e100_codec_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<e100_codec_ctrl> sptr; + + static const uhd::gain_range_t tx_pga_gain_range; + static const uhd::gain_range_t rx_pga_gain_range; + + /*! + * Make a new codec control object. + * \param iface the spi iface object + * \return the codec control object + */ + static sptr make(uhd::spi_iface::sptr iface); + + //! aux adc identifier constants + enum aux_adc_t{ + AUX_ADC_A2 = 0xA2, + AUX_ADC_A1 = 0xA1, + AUX_ADC_B2 = 0xB2, + AUX_ADC_B1 = 0xB1 + }; + + /*! + * Read an auxiliary adc: + * The internals remember which aux adc was read last. + * Therefore, the aux adc switch is only changed as needed. + * \param which which of the 4 adcs + * \return a value in volts + */ + virtual double read_aux_adc(aux_adc_t which) = 0; + + //! aux dac identifier constants + enum aux_dac_t{ + AUX_DAC_A = 0xA, + AUX_DAC_B = 0xB, + AUX_DAC_C = 0xC, + AUX_DAC_D = 0xD //really the sigma delta output + }; + + /*! + * Write an auxiliary dac. + * \param which which of the 4 dacs + * \param volts the level in in volts + */ + virtual void write_aux_dac(aux_dac_t which, double volts) = 0; + + //! Set the TX PGA gain + virtual void set_tx_pga_gain(double gain) = 0; + + //! Get the TX PGA gain + virtual double get_tx_pga_gain(void) = 0; + + //! Set the RX PGA gain ('A' or 'B') + virtual void set_rx_pga_gain(double gain, char which) = 0; + + //! Get the RX PGA gain ('A' or 'B') + virtual double get_rx_pga_gain(char which) = 0; +}; + +#endif /* INCLUDED_USRP_E100_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/e100/dboard_iface.cpp b/host/lib/usrp/e100/dboard_iface.cpp new file mode 100644 index 000000000..6afc7bc48 --- /dev/null +++ b/host/lib/usrp/e100/dboard_iface.cpp @@ -0,0 +1,258 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "gpio_core_200.hpp" +#include <uhd/types/serial.hpp> +#include "e100_regs.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> +#include <linux/usrp_e.h> //i2c and spi constants + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class e100_dboard_iface : public dboard_iface{ +public: + + e100_dboard_iface( + wb_iface::sptr wb_iface, + i2c_iface::sptr i2c_iface, + spi_iface::sptr spi_iface, + e100_clock_ctrl::sptr clock, + e100_codec_ctrl::sptr codec + ){ + _wb_iface = wb_iface; + _i2c_iface = i2c_iface; + _spi_iface = spi_iface; + _clock = clock; + _codec = codec; + _gpio = gpio_core_200::make(_wb_iface, E100_REG_SR_ADDR(UE_SR_GPIO), E100_REG_RB_GPIO); + + //init the clock rate shadows + this->set_clock_rate(UNIT_RX, _clock->get_fpga_clock_rate()); + this->set_clock_rate(UNIT_TX, _clock->get_fpga_clock_rate()); + } + + ~e100_dboard_iface(void){ + /* NOP */ + } + + special_props_t get_special_props(void){ + special_props_t props; + props.soft_clock_divider = false; + props.mangle_i2c_addrs = false; + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, double); + double read_aux_adc(unit_t, aux_adc_t); + + void _set_pin_ctrl(unit_t, boost::uint16_t); + void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); + void _set_gpio_ddr(unit_t, boost::uint16_t); + void _set_gpio_out(unit_t, boost::uint16_t); + void set_gpio_debug(unit_t, int); + boost::uint16_t read_gpio(unit_t); + + void write_i2c(boost::uint8_t, const byte_vector_t &); + byte_vector_t read_i2c(boost::uint8_t, size_t); + + void write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + boost::uint32_t read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + void set_clock_rate(unit_t, double); + std::vector<double> get_clock_rates(unit_t); + double get_clock_rate(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + +private: + wb_iface::sptr _wb_iface; + i2c_iface::sptr _i2c_iface; + spi_iface::sptr _spi_iface; + e100_clock_ctrl::sptr _clock; + e100_codec_ctrl::sptr _codec; + gpio_core_200::sptr _gpio; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_e100_dboard_iface( + wb_iface::sptr wb_iface, + i2c_iface::sptr i2c_iface, + spi_iface::sptr spi_iface, + e100_clock_ctrl::sptr clock, + e100_codec_ctrl::sptr codec +){ + return dboard_iface::sptr(new e100_dboard_iface(wb_iface, i2c_iface, spi_iface, clock, codec)); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +void e100_dboard_iface::set_clock_rate(unit_t unit, double rate){ + switch(unit){ + case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); + case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + } +} + +std::vector<double> e100_dboard_iface::get_clock_rates(unit_t unit){ + switch(unit){ + case UNIT_RX: return _clock->get_rx_dboard_clock_rates(); + case UNIT_TX: return _clock->get_tx_dboard_clock_rates(); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +double e100_dboard_iface::get_clock_rate(unit_t unit){ + switch(unit){ + case UNIT_RX: return _clock->get_rx_clock_rate(); + case UNIT_TX: return _clock->get_tx_clock_rate(); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +void e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ + switch(unit){ + case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); + case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + } +} + +double e100_dboard_iface::get_codec_rate(unit_t){ + return _clock->get_fpga_clock_rate(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void e100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ + return _gpio->set_pin_ctrl(unit, value); +} + +void e100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ + return _gpio->set_gpio_ddr(unit, value); +} + +void e100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ + return _gpio->set_gpio_out(unit, value); +} + +boost::uint16_t e100_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); +} + +void e100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ + return _gpio->set_atr_reg(unit, atr, value); +} + +void e100_dboard_iface::set_gpio_debug(unit_t, int){ + throw uhd::not_implemented_error("no set_gpio_debug implemented"); +} + +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ + switch(unit){ + case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB; + case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB; + } + UHD_THROW_INVALID_CODE_PATH(); +} + +void e100_dboard_iface::write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + _spi_iface->write_spi(unit_to_otw_spi_dev(unit), config, data, num_bits); +} + +boost::uint32_t e100_dboard_iface::read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + return _spi_iface->read_spi(unit_to_otw_spi_dev(unit), config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void e100_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ + return _i2c_iface->write_i2c(addr, bytes); +} + +byte_vector_t e100_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ + return _i2c_iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void e100_dboard_iface::write_aux_dac(dboard_iface::unit_t, aux_dac_t which, double value){ + //same aux dacs for each unit + static const uhd::dict<aux_dac_t, e100_codec_ctrl::aux_dac_t> which_to_aux_dac = map_list_of + (AUX_DAC_A, e100_codec_ctrl::AUX_DAC_A) + (AUX_DAC_B, e100_codec_ctrl::AUX_DAC_B) + (AUX_DAC_C, e100_codec_ctrl::AUX_DAC_C) + (AUX_DAC_D, e100_codec_ctrl::AUX_DAC_D) + ; + _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +double e100_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, aux_adc_t which){ + static const uhd::dict< + unit_t, uhd::dict<aux_adc_t, e100_codec_ctrl::aux_adc_t> + > unit_to_which_to_aux_adc = map_list_of + (UNIT_RX, map_list_of + (AUX_ADC_A, e100_codec_ctrl::AUX_ADC_A1) + (AUX_ADC_B, e100_codec_ctrl::AUX_ADC_B1) + ) + (UNIT_TX, map_list_of + (AUX_ADC_A, e100_codec_ctrl::AUX_ADC_A2) + (AUX_ADC_B, e100_codec_ctrl::AUX_ADC_B2) + ) + ; + return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/e100/e100_ctrl.cpp b/host/lib/usrp/e100/e100_ctrl.cpp new file mode 100644 index 000000000..eb529c9c1 --- /dev/null +++ b/host/lib/usrp/e100/e100_ctrl.cpp @@ -0,0 +1,355 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include "e100_regs.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <sys/ioctl.h> //ioctl +#include <fcntl.h> //open, close +#include <linux/usrp_e.h> //ioctl structures and constants +#include <boost/thread/thread.hpp> //sleep +#include <boost/thread/mutex.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <fstream> + +using namespace uhd; + +/*********************************************************************** + * Sysfs GPIO wrapper class + **********************************************************************/ +class gpio{ +public: + gpio(const int num, const std::string &dir) : _num(num){ + this->set_xport("export"); + this->set_dir(dir); + _value_file.open(str(boost::format("/sys/class/gpio/gpio%d/value") % num).c_str(), std::ios_base::in | std::ios_base::out); + } + ~gpio(void){ + _value_file.close(); + this->set_dir("in"); + this->set_xport("unexport"); + } + void operator()(const int val){ + _value_file << val << std::endl << std::flush; + } + int operator()(void){ + std::string val; + std::getline(_value_file, val); + _value_file.seekg(0); + return int(val.at(0) - '0') & 0x1; + } +private: + void set_xport(const std::string &xport){ + std::ofstream export_file(("/sys/class/gpio/" + xport).c_str()); + export_file << _num << std::endl << std::flush; + export_file.close(); + } + void set_dir(const std::string &dir){ + std::ofstream dir_file(str(boost::format("/sys/class/gpio/gpio%d/direction") % _num).c_str()); + dir_file << dir << std::endl << std::flush; + dir_file.close(); + } + const int _num; + std::fstream _value_file; +}; + +/*********************************************************************** + * Aux spi implementation + **********************************************************************/ +class aux_spi_iface_impl : public spi_iface{ +public: + aux_spi_iface_impl(void): + spi_sclk_gpio(65, "out"), + spi_sen_gpio(186, "out"), + spi_mosi_gpio(145, "out"), + spi_miso_gpio(147, "in"){} + + boost::uint32_t transact_spi( + int, const spi_config_t &, //not used params + boost::uint32_t bits, + size_t num_bits, + bool readback + ){ + boost::uint32_t rb_bits = 0; + this->spi_sen_gpio(0); + + for (size_t i = 0; i < num_bits; i++){ + this->spi_sclk_gpio(0); + this->spi_mosi_gpio((bits >> (num_bits-i-1)) & 0x1); + boost::this_thread::sleep(boost::posix_time::microseconds(10)); + if (readback) rb_bits = (rb_bits << 1) | this->spi_miso_gpio(); + this->spi_sclk_gpio(1); + boost::this_thread::sleep(boost::posix_time::microseconds(10)); + } + + this->spi_sen_gpio(1); + boost::this_thread::sleep(boost::posix_time::microseconds(100)); + return rb_bits; + } + +private: + gpio spi_sclk_gpio, spi_sen_gpio, spi_mosi_gpio, spi_miso_gpio; +}; + +uhd::spi_iface::sptr e100_ctrl::make_aux_spi_iface(void){ + return uhd::spi_iface::sptr(new aux_spi_iface_impl()); +} + +/*********************************************************************** + * I2C device node implementation wrapper + **********************************************************************/ +#include <linux/i2c-dev.h> +#include <linux/i2c.h> +class i2c_dev_iface : public i2c_iface{ +public: + i2c_dev_iface(const std::string &node){ + if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ + throw uhd::io_error("Failed to open " + node); + } + } + + ~i2c_dev_iface(void){ + ::close(_node_fd); + } + + void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ + byte_vector_t rw_bytes(bytes); + + //setup the message + i2c_msg msg; + msg.addr = addr; + msg.flags = 0; + msg.len = bytes.size(); + msg.buf = &rw_bytes.front(); + + //setup the data + i2c_rdwr_ioctl_data data; + data.msgs = &msg; + data.nmsgs = 1; + + //call the ioctl + UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); + } + + byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ + byte_vector_t bytes(num_bytes); + + //setup the message + i2c_msg msg; + msg.addr = addr; + msg.flags = I2C_M_RD; + msg.len = bytes.size(); + msg.buf = &bytes.front(); + + //setup the data + i2c_rdwr_ioctl_data data; + data.msgs = &msg; + data.nmsgs = 1; + + //call the ioctl + UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); + + return bytes; + } + +private: int _node_fd; +}; + +uhd::i2c_iface::sptr e100_ctrl::make_dev_i2c_iface(const std::string &node){ + return uhd::i2c_iface::sptr(new i2c_dev_iface(node)); +} + +/*********************************************************************** + * UART control implementation + **********************************************************************/ +#include <termios.h> +#include <cstring> +class uart_dev_iface : public uart_iface{ +public: + uart_dev_iface(const std::string &node){ + if ((_node_fd = ::open(node.c_str(), O_RDWR | O_NONBLOCK)) < 0){ + throw uhd::io_error("Failed to open " + node); + } + + //init the tty settings w/ termios + termios tio; + std::memset(&tio,0,sizeof(tio)); + tio.c_iflag = IGNCR; //Ignore CR + tio.c_oflag = OPOST | ONLCR; //Map NL to CR-NL on output + tio.c_cflag = CS8 | CREAD | CLOCAL; // 8n1 + tio.c_lflag = 0; + + cfsetospeed(&tio, B115200); // 115200 baud + cfsetispeed(&tio, B115200); // 115200 baud + + tcsetattr(_node_fd, TCSANOW, &tio); + } + + void write_uart(const std::string &buf){ + const ssize_t ret = ::write(_node_fd, buf.c_str(), buf.size()); + if (size_t(ret) != buf.size()) UHD_LOG << ret; + } + + std::string read_uart(double timeout){ + const boost::system_time exit_time = boost::get_system_time() + boost::posix_time::milliseconds(long(timeout*1000)); + + std::string line; + while(true){ + char ch; + const ssize_t ret = ::read(_node_fd, &ch, 1); + + //got a character -> process it + if (ret == 1){ + const bool flush = ch == '\n' or ch == '\r'; + if (flush and line.empty()) continue; //avoid flushing on empty lines + line += std::string(1, ch); + if (flush) break; + } + + //didnt get a character, check the timeout + else if (boost::get_system_time() > exit_time){ + break; + } + + //otherwise sleep for a bit + else{ + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + } + } + return line; + } + +private: int _node_fd; +}; + +uhd::uart_iface::sptr e100_ctrl::make_gps_uart_iface(const std::string &node){ + return uhd::uart_iface::sptr(new uart_dev_iface(node)); +} + +/*********************************************************************** + * USRP-E100 control implementation + **********************************************************************/ +class e100_ctrl_impl : public e100_ctrl{ +public: + + int get_file_descriptor(void){ + return _node_fd; + } + + /******************************************************************* + * Structors + ******************************************************************/ + e100_ctrl_impl(const std::string &node){ + UHD_MSG(status) << "Opening device node " << node << "..." << std::endl; + + //open the device node and check file descriptor + if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ + throw uhd::io_error("Failed to open " + node); + } + + //check the module compatibility number + int module_compat_num = ::ioctl(_node_fd, USRP_E_GET_COMPAT_NUMBER, NULL); + if (module_compat_num != USRP_E_COMPAT_NUMBER){ + throw uhd::runtime_error(str(boost::format( + "Expected module compatibility number 0x%x, but got 0x%x:\n" + "The module build is not compatible with the host code build." + ) % USRP_E_COMPAT_NUMBER % module_compat_num)); + } + + //perform a global reset after opening + this->poke32(E100_REG_GLOBAL_RESET, 0); + } + + ~e100_ctrl_impl(void){ + ::close(_node_fd); + } + + /******************************************************************* + * IOCTL: provides the communication base for all other calls + ******************************************************************/ + void ioctl(int request, void *mem){ + boost::mutex::scoped_lock lock(_ioctl_mutex); + + if (::ioctl(_node_fd, request, mem) < 0){ + throw uhd::os_error(str( + boost::format("ioctl failed with request %d") % request + )); + } + } + /******************************************************************* + * Peek and Poke + ******************************************************************/ + void poke32(wb_addr_type addr, boost::uint32_t value){ + //load the data struct + usrp_e_ctl32 data; + data.offset = addr; + data.count = 1; + data.buf[0] = value; + + //call the ioctl + this->ioctl(USRP_E_WRITE_CTL32, &data); + } + + void poke16(wb_addr_type addr, boost::uint16_t value){ + //load the data struct + usrp_e_ctl16 data; + data.offset = addr; + data.count = 1; + data.buf[0] = value; + + //call the ioctl + this->ioctl(USRP_E_WRITE_CTL16, &data); + } + + boost::uint32_t peek32(wb_addr_type addr){ + //load the data struct + usrp_e_ctl32 data; + data.offset = addr; + data.count = 1; + + //call the ioctl + this->ioctl(USRP_E_READ_CTL32, &data); + + return data.buf[0]; + } + + boost::uint16_t peek16(wb_addr_type addr){ + //load the data struct + usrp_e_ctl16 data; + data.offset = addr; + data.count = 1; + + //call the ioctl + this->ioctl(USRP_E_READ_CTL16, &data); + + return data.buf[0]; + } + +private: + int _node_fd; + boost::mutex _ioctl_mutex; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +e100_ctrl::sptr e100_ctrl::make(const std::string &node){ + return sptr(new e100_ctrl_impl(node)); +} diff --git a/host/lib/usrp/e100/e100_ctrl.hpp b/host/lib/usrp/e100/e100_ctrl.hpp new file mode 100644 index 000000000..fd66791d4 --- /dev/null +++ b/host/lib/usrp/e100/e100_ctrl.hpp @@ -0,0 +1,48 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CTRL_HPP +#define INCLUDED_B100_CTRL_HPP + +#include "wb_iface.hpp" +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class e100_ctrl : boost::noncopyable, public wb_iface{ +public: + typedef boost::shared_ptr<e100_ctrl> sptr; + + //! Make a new controller for E100 + static sptr make(const std::string &node); + + //! Make an i2c iface for the i2c device node + static uhd::i2c_iface::sptr make_dev_i2c_iface(const std::string &node); + + //! Make an i2c iface for the i2c device node + static uhd::spi_iface::sptr make_aux_spi_iface(void); + + //! Make a uart iface for the uart device node + static uhd::uart_iface::sptr make_gps_uart_iface(const std::string &node); + + virtual void ioctl(int request, void *mem) = 0; + + virtual int get_file_descriptor(void) = 0; + +}; + +#endif /* INCLUDED_B100_CTRL_HPP */ diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp new file mode 100644 index 000000000..bb135b646 --- /dev/null +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -0,0 +1,494 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "apply_corrections.hpp" +#include "e100_impl.hpp" +#include "e100_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/functional/hash.hpp> +#include <boost/assign/list_of.hpp> +#include <fstream> + +using namespace uhd; +using namespace uhd::usrp; +namespace fs = boost::filesystem; + +//////////////////////////////////////////////////////////////////////// +// I2C addresses +//////////////////////////////////////////////////////////////////////// +#define I2C_DEV_EEPROM 0x50 // 24LC02[45]: 7-bits 1010xxx +#define I2C_ADDR_MBOARD (I2C_DEV_EEPROM | 0x0) +#define I2C_ADDR_TX_DB (I2C_DEV_EEPROM | 0x4) +#define I2C_ADDR_RX_DB (I2C_DEV_EEPROM | 0x5) + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t e100_find(const device_addr_t &hint){ + device_addrs_t e100_addrs; + + //return an empty list of addresses when type is set to non-usrp-e + if (hint.has_key("type") and hint["type"] != "e100") return e100_addrs; + + //device node not provided, assume its 0 + if (not hint.has_key("node")){ + device_addr_t new_addr = hint; + new_addr["node"] = "/dev/usrp_e0"; + return e100_find(new_addr); + } + + //use the given device node name + if (fs::exists(hint["node"])){ + device_addr_t new_addr; + new_addr["type"] = "e100"; + new_addr["node"] = fs::system_complete(fs::path(hint["node"])).string(); + try{ + i2c_iface::sptr i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); + const mboard_eeprom_t mb_eeprom(*i2c_iface, mboard_eeprom_t::MAP_E100); + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = mb_eeprom["serial"]; + } + catch(const std::exception &e){ + new_addr["name"] = ""; + new_addr["serial"] = ""; + } + if ( + (not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) + ){ + e100_addrs.push_back(new_addr); + } + } + + return e100_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static size_t hash_fpga_file(const std::string &file_path){ + size_t hash = 0; + std::ifstream file(file_path.c_str()); + if (not file.good()) throw uhd::io_error("cannot open fpga file for read: " + file_path); + while (file.good()) boost::hash_combine(hash, file.get()); + file.close(); + return hash; +} + +static device::sptr e100_make(const device_addr_t &device_addr){ + return device::sptr(new e100_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_e100_device){ + device::register_device(&e100_find, &e100_make); +} + +static const uhd::dict<std::string, std::string> model_to_fpga_file_name = boost::assign::map_list_of + ("E100", "usrp_e100_fpga_v2.bin") + ("E110", "usrp_e110_fpga.bin") +; + +/*********************************************************************** + * Structors + **********************************************************************/ +e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ + _tree = property_tree::make(); + + //setup the main interface into fpga + const std::string node = device_addr["node"]; + _fpga_ctrl = e100_ctrl::make(node); + + //read the eeprom so we can determine the hardware + _dev_i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); + const mboard_eeprom_t mb_eeprom(*_dev_i2c_iface, mboard_eeprom_t::MAP_E100); + + //determine the model string for this device + const std::string model = device_addr.get("model", mb_eeprom.get("model", "")); + if (not model_to_fpga_file_name.has_key(model)) throw uhd::runtime_error(str(boost::format( + "\n" + " The specified model string \"%s\" is not recognized.\n" + " Perhaps the EEPROM is uninitialized, missing, or damaged.\n" + " Or, a monitor is pirating the I2C address of the EEPROM.\n" + ) % model)); + + //extract the fpga path and compute hash + const std::string default_fpga_file_name = model_to_fpga_file_name[model]; + const std::string e100_fpga_image = find_image_path(device_addr.get("fpga", default_fpga_file_name)); + const boost::uint32_t file_hash = boost::uint32_t(hash_fpga_file(e100_fpga_image)); + + //When the hash does not match: + // - close the device node + // - load the fpga bin file + // - re-open the device node + if (_fpga_ctrl->peek32(E100_REG_RB_MISC_TEST32) != file_hash){ + _fpga_ctrl.reset(); + e100_load_fpga(e100_fpga_image); + _fpga_ctrl = e100_ctrl::make(node); + } + + //setup clock control here to ensure that the FPGA has a good clock before we continue + bool dboard_clocks_diff = true; + if (mb_eeprom.get("revision", "0") == "3") dboard_clocks_diff = false; + else if (mb_eeprom.get("revision", "0") == "4") dboard_clocks_diff = true; + else UHD_MSG(warning) + << "Unknown E1XX revision number!\n" + << "defaulting to differential dboard clocks to be safe.\n" + << std::endl; + const double master_clock_rate = device_addr.cast<double>("master_clock_rate", E100_DEFAULT_CLOCK_RATE); + _aux_spi_iface = e100_ctrl::make_aux_spi_iface(); + _clock_ctrl = e100_clock_ctrl::make(_aux_spi_iface, master_clock_rate, dboard_clocks_diff); + + //Perform wishbone readback tests, these tests also write the hash + bool test_fail = false; + UHD_MSG(status) << "Performing wishbone readback test... " << std::flush; + for (size_t i = 0; i < 100; i++){ + _fpga_ctrl->poke32(E100_REG_SR_MISC_TEST32, file_hash); + test_fail = _fpga_ctrl->peek32(E100_REG_RB_MISC_TEST32) != file_hash; + if (test_fail) break; //exit loop on any failure + } + UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; + + if (test_fail) UHD_MSG(error) << boost::format( + "The FPGA is either clocked improperly\n" + "or the FPGA build is not compatible.\n" + "Subsequent errors may follow...\n" + ); + + //check that the compatibility is correct + this->check_fpga_compat(); + + //////////////////////////////////////////////////////////////////// + // Create controller objects + //////////////////////////////////////////////////////////////////// + _fpga_i2c_ctrl = i2c_core_100::make(_fpga_ctrl, E100_REG_SLAVE(3)); + _fpga_spi_ctrl = spi_core_100::make(_fpga_ctrl, E100_REG_SLAVE(2)); + _data_transport = e100_make_mmap_zero_copy(_fpga_ctrl); + + //////////////////////////////////////////////////////////////////// + // Initialize the properties tree + //////////////////////////////////////////////////////////////////// + _tree->create<std::string>("/name").set("E-Series Device"); + const fs_path mb_path = "/mboards/0"; + _tree->create<std::string>(mb_path / "name").set(str(boost::format("%s (euewanee)") % model)); + + //////////////////////////////////////////////////////////////////// + // setup the mboard eeprom + //////////////////////////////////////////////////////////////////// + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(mb_eeprom) + .subscribe(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); + + //////////////////////////////////////////////////////////////////// + // create clock control objects + //////////////////////////////////////////////////////////////////// + //^^^ clock created up top, just reg props here... ^^^ + _tree->create<double>(mb_path / "tick_rate") + .publish(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) + .subscribe(boost::bind(&e100_impl::update_tick_rate, this, _1)); + + //////////////////////////////////////////////////////////////////// + // create codec control objects + //////////////////////////////////////////////////////////////////// + _codec_ctrl = e100_codec_ctrl::make(_fpga_spi_ctrl); + const fs_path rx_codec_path = mb_path / "rx_codecs/A"; + const fs_path tx_codec_path = mb_path / "tx_codecs/A"; + _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); + _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(e100_codec_ctrl::rx_pga_gain_range); + _tree->create<double>(rx_codec_path / "gains/pga/value") + .coerce(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); + _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); + _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(e100_codec_ctrl::tx_pga_gain_range); + _tree->create<double>(tx_codec_path / "gains/pga/value") + .subscribe(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) + .publish(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); + + //////////////////////////////////////////////////////////////////// + // and do the misc mboard sensors + //////////////////////////////////////////////////////////////////// + _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") + .publish(boost::bind(&e100_impl::get_ref_locked, this)); + + //////////////////////////////////////////////////////////////////// + // Create the GPSDO control + //////////////////////////////////////////////////////////////////// + try{ + _gps = gps_ctrl::make(e100_ctrl::make_gps_uart_iface(E100_UART_DEV_NODE)); + } + catch(std::exception &e){ + UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; + } + if (_gps.get() != NULL and _gps->gps_detected()){ + BOOST_FOREACH(const std::string &name, _gps->get_sensors()){ + _tree->create<sensor_value_t>(mb_path / "sensors" / name) + .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name)); + } + } + + //////////////////////////////////////////////////////////////////// + // create frontend control objects + //////////////////////////////////////////////////////////////////// + _rx_fe = rx_frontend_core_200::make(_fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_FRONT)); + _tx_fe = tx_frontend_core_200::make(_fpga_ctrl, E100_REG_SR_ADDR(UE_SR_TX_FRONT)); + + _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") + .subscribe(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); + _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") + .subscribe(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); + + const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; + const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; + + _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") + .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) + .set(true); + _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") + .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) + .set(std::polar<double>(1.0, 0.0)); + _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") + .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) + .set(std::polar<double>(1.0, 0.0)); + + //////////////////////////////////////////////////////////////////// + // create rx dsp control objects + //////////////////////////////////////////////////////////////////// + _rx_dsps.push_back(rx_dsp_core_200::make( + _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_DSP0), E100_REG_SR_ADDR(UE_SR_RX_CTRL0), E100_RX_SID_BASE + 0 + )); + _rx_dsps.push_back(rx_dsp_core_200::make( + _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_DSP1), E100_REG_SR_ADDR(UE_SR_RX_CTRL1), E100_RX_SID_BASE + 1 + )); + for (size_t dspno = 0; dspno < _rx_dsps.size(); dspno++){ + _rx_dsps[dspno]->set_link_rate(E100_RX_LINK_RATE_BPS); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); + fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); + _tree->create<meta_range_t>(rx_dsp_path / "rate/range") + .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); + _tree->create<double>(rx_dsp_path / "rate/value") + .set(1e6) //some default + .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) + .subscribe(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1)); + _tree->create<double>(rx_dsp_path / "freq/value") + .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); + _tree->create<meta_range_t>(rx_dsp_path / "freq/range") + .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); + } + + //////////////////////////////////////////////////////////////////// + // create tx dsp control objects + //////////////////////////////////////////////////////////////////// + _tx_dsp = tx_dsp_core_200::make( + _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_TX_DSP), E100_REG_SR_ADDR(UE_SR_TX_CTRL), E100_TX_ASYNC_SID + ); + _tx_dsp->set_link_rate(E100_TX_LINK_RATE_BPS); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); + _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") + .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); + _tree->create<double>(mb_path / "tx_dsps/0/rate/value") + .set(1e6) //some default + .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) + .subscribe(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1)); + _tree->create<double>(mb_path / "tx_dsps/0/freq/value") + .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); + _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") + .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + + //////////////////////////////////////////////////////////////////// + // create time control objects + //////////////////////////////////////////////////////////////////// + time64_core_200::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_hi_now = E100_REG_RB_TIME_NOW_HI; + time64_rb_bases.rb_lo_now = E100_REG_RB_TIME_NOW_LO; + time64_rb_bases.rb_hi_pps = E100_REG_RB_TIME_PPS_HI; + time64_rb_bases.rb_lo_pps = E100_REG_RB_TIME_PPS_LO; + _time64 = time64_core_200::make( + _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_TIME64), time64_rb_bases + ); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); + _tree->create<time_spec_t>(mb_path / "time/now") + .publish(boost::bind(&time64_core_200::get_time_now, _time64)) + .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); + _tree->create<time_spec_t>(mb_path / "time/pps") + .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) + .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); + //setup time source props + _tree->create<std::string>(mb_path / "time_source/value") + .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); + _tree->create<std::vector<std::string> >(mb_path / "time_source/options") + .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); + //setup reference source props + _tree->create<std::string>(mb_path / "clock_source/value") + .subscribe(boost::bind(&e100_impl::update_clock_source, this, _1)); + static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); + _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); + + //////////////////////////////////////////////////////////////////// + // create user-defined control objects + //////////////////////////////////////////////////////////////////// + _user = user_settings_core_200::make(_fpga_ctrl, E100_REG_SR_ADDR(UE_SR_USER_REGS)); + _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") + .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + + //////////////////////////////////////////////////////////////////// + // create dboard control objects + //////////////////////////////////////////////////////////////////// + + //read the dboard eeprom to extract the dboard ids + dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; + rx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_RX_DB); + tx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB); + gdb_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB ^ 5); + + //create the properties and register subscribers + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") + .set(rx_db_eeprom) + .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") + .set(tx_db_eeprom) + .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") + .set(gdb_eeprom) + .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); + + //create a new dboard interface and manager + _dboard_iface = make_e100_dboard_iface(_fpga_ctrl, _fpga_i2c_ctrl, _fpga_spi_ctrl, _clock_ctrl, _codec_ctrl); + _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); + _dboard_manager = dboard_manager::make( + rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, + _dboard_iface, _tree->subtree(mb_path / "dboards/A") + ); + + //bind frontend corrections to the dboard freq props + const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ + _tree->access<double>(db_tx_fe_path / name / "freq" / "value") + .subscribe(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1)); + } + const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ + _tree->access<double>(db_rx_fe_path / name / "freq" / "value") + .subscribe(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1)); + } + + //initialize io handling + this->io_init(); + + //////////////////////////////////////////////////////////////////// + // do some post-init tasks + //////////////////////////////////////////////////////////////////// + this->update_rates(); + + _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter + .subscribe(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + + //reset cordic rates and their properties to zero + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ + _tree->access<double>(mb_path / "rx_dsps" / name / "freq" / "value").set(0.0); + } + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ + _tree->access<double>(mb_path / "tx_dsps" / name / "freq" / "value").set(0.0); + } + + _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(mb_path / "dboards/A/rx_frontends").at(0))); + _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(mb_path / "dboards/A/tx_frontends").at(0))); + _tree->access<std::string>(mb_path / "clock_source/value").set("internal"); + _tree->access<std::string>(mb_path / "time_source/value").set("none"); + + //GPS installed: use external ref, time, and init time spec + if (_gps.get() != NULL and _gps->gps_detected()){ + UHD_MSG(status) << "Setting references to the internal GPSDO" << std::endl; + _tree->access<std::string>(mb_path / "time_source/value").set("external"); + _tree->access<std::string>(mb_path / "clock_source/value").set("external"); + UHD_MSG(status) << "Initializing time to the internal GPSDO" << std::endl; + _time64->set_time_next_pps(time_spec_t(time_t(_gps->get_sensor("gps_time").to_int()+1))); + } + +} + +e100_impl::~e100_impl(void){ + /* NOP */ +} + +double e100_impl::update_rx_codec_gain(const double gain){ + //set gain on both I and Q, readback on one + //TODO in the future, gains should have individual control + _codec_ctrl->set_rx_pga_gain(gain, 'A'); + _codec_ctrl->set_rx_pga_gain(gain, 'B'); + return _codec_ctrl->get_rx_pga_gain('A'); +} + +void e100_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom){ + mb_eeprom.commit(*_dev_i2c_iface, mboard_eeprom_t::MAP_E100); +} + +void e100_impl::set_db_eeprom(const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ + if (type == "rx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_RX_DB); + if (type == "tx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB); + if (type == "gdb") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB ^ 5); +} + +void e100_impl::update_clock_source(const std::string &source){ + if (source == "auto") _clock_ctrl->use_auto_ref(); + else if (source == "internal") _clock_ctrl->use_internal_ref(); + else if (source == "external") _clock_ctrl->use_external_ref(); + else throw uhd::runtime_error("unhandled clock configuration reference source: " + source); +} + +sensor_value_t e100_impl::get_ref_locked(void){ + const bool lock = _clock_ctrl->get_locked(); + return sensor_value_t("Ref", lock, "locked", "unlocked"); +} + +void e100_impl::check_fpga_compat(void){ + const boost::uint32_t fpga_compat_num = _fpga_ctrl->peek32(E100_REG_RB_COMPAT); + boost::uint16_t fpga_major = fpga_compat_num >> 16, fpga_minor = fpga_compat_num & 0xffff; + if (fpga_major == 0){ //old version scheme + fpga_major = fpga_minor; + fpga_minor = 0; + } + if (fpga_major != E100_FPGA_COMPAT_NUM){ + throw uhd::runtime_error(str(boost::format( + "Expected FPGA compatibility number %d, but got %d:\n" + "The FPGA build is not compatible with the host code build." + ) % int(E100_FPGA_COMPAT_NUM) % fpga_major)); + } + _tree->create<std::string>("/mboards/0/fpga_version").set(str(boost::format("%u.%u") % fpga_major % fpga_minor)); +} + +void e100_impl::set_rx_fe_corrections(const double lo_freq){ + apply_rx_fe_corrections(this->get_tree()->subtree("/mboards/0"), "A", lo_freq); +} + +void e100_impl::set_tx_fe_corrections(const double lo_freq){ + apply_tx_fe_corrections(this->get_tree()->subtree("/mboards/0"), "A", lo_freq); +} diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp new file mode 100644 index 000000000..1d36cb2ac --- /dev/null +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -0,0 +1,140 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "spi_core_100.hpp" +#include "i2c_core_100.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "rx_dsp_core_200.hpp" +#include "tx_dsp_core_200.hpp" +#include "time64_core_200.hpp" +#include "user_settings_core_200.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/weak_ptr.hpp> + +#ifndef INCLUDED_E100_IMPL_HPP +#define INCLUDED_E100_IMPL_HPP + +uhd::transport::zero_copy_if::sptr e100_make_mmap_zero_copy(e100_ctrl::sptr iface); + +// = gpmc_clock_rate/clk_div/cycles_per_transaction*bytes_per_transaction +static const double E100_RX_LINK_RATE_BPS = 166e6/3/2*2; +static const double E100_TX_LINK_RATE_BPS = 166e6/3/1*2; +static const std::string E100_I2C_DEV_NODE = "/dev/i2c-3"; +static const std::string E100_UART_DEV_NODE = "/dev/ttyO0"; +static const boost::uint16_t E100_FPGA_COMPAT_NUM = 0x09; +static const boost::uint32_t E100_RX_SID_BASE = 2; +static const boost::uint32_t E100_TX_ASYNC_SID = 1; +static const double E100_DEFAULT_CLOCK_RATE = 64e6; + +//! load an fpga image from a bin file into the usrp-e fpga +extern void e100_load_fpga(const std::string &bin_file); + +//! Make an e100 dboard interface +uhd::usrp::dboard_iface::sptr make_e100_dboard_iface( + wb_iface::sptr wb_iface, + uhd::i2c_iface::sptr i2c_iface, + uhd::spi_iface::sptr spi_iface, + e100_clock_ctrl::sptr clock, + e100_codec_ctrl::sptr codec +); + +/*! + * USRP-E100 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class e100_impl : public uhd::device{ +public: + //structors + e100_impl(const uhd::device_addr_t &); + ~e100_impl(void); + + //the io interface + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + bool recv_async_msg(uhd::async_metadata_t &, double); + +private: + uhd::property_tree::sptr _tree; + + //controllers + spi_core_100::sptr _fpga_spi_ctrl; + i2c_core_100::sptr _fpga_i2c_ctrl; + rx_frontend_core_200::sptr _rx_fe; + tx_frontend_core_200::sptr _tx_fe; + std::vector<rx_dsp_core_200::sptr> _rx_dsps; + tx_dsp_core_200::sptr _tx_dsp; + time64_core_200::sptr _time64; + user_settings_core_200::sptr _user; + e100_clock_ctrl::sptr _clock_ctrl; + e100_codec_ctrl::sptr _codec_ctrl; + e100_ctrl::sptr _fpga_ctrl; + uhd::i2c_iface::sptr _dev_i2c_iface; + uhd::spi_iface::sptr _aux_spi_iface; + uhd::gps_ctrl::sptr _gps; + + //transports + uhd::transport::zero_copy_if::sptr _data_transport; + + //dboard stuff + uhd::usrp::dboard_manager::sptr _dboard_manager; + uhd::usrp::dboard_iface::sptr _dboard_iface; + + //handle io stuff + UHD_PIMPL_DECL(io_impl) _io_impl; + void io_init(void); + + //device properties interface + uhd::property_tree::sptr get_tree(void) const{ + return _tree; + } + + std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; + std::vector<boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; + + double update_rx_codec_gain(const double); //sets A and B at once + void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); + void set_db_eeprom(const std::string &, const uhd::usrp::dboard_eeprom_t &); + void update_tick_rate(const double rate); + void update_rx_samp_rate(const size_t, const double rate); + void update_tx_samp_rate(const size_t, const double rate); + void update_rates(void); + void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void update_clock_source(const std::string &); + uhd::sensor_value_t get_ref_locked(void); + void check_fpga_compat(void); + void set_rx_fe_corrections(const double); + void set_tx_fe_corrections(const double); + +}; + +#endif /* INCLUDED_E100_IMPL_HPP */ diff --git a/host/lib/usrp/e100/e100_mmap_zero_copy.cpp b/host/lib/usrp/e100/e100_mmap_zero_copy.cpp new file mode 100644 index 000000000..cdb7094f4 --- /dev/null +++ b/host/lib/usrp/e100/e100_mmap_zero_copy.cpp @@ -0,0 +1,269 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include <uhd/transport/zero_copy.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/exception.hpp> +#include <linux/usrp_e.h> +#include <sys/mman.h> //mmap +#include <unistd.h> //getpagesize +#include <poll.h> //poll +#include <vector> + +using namespace uhd; +using namespace uhd::transport; + +#define fp_verbose false //fast-path verbose +static const size_t poll_breakout = 10; //how many poll timeouts constitute a full timeout + +/*********************************************************************** + * Reusable managed receiver buffer: + * - The buffer knows how to claim and release a frame. + **********************************************************************/ +class e100_mmap_zero_copy_mrb : public managed_recv_buffer{ +public: + e100_mmap_zero_copy_mrb(void *mem, ring_buffer_info *info): + _mem(mem), _info(info) { /* NOP */ } + + void release(void){ + if (_info->flags != RB_USER_PROCESS) return; + if (fp_verbose) UHD_LOGV(always) << "recv buff: release" << std::endl; + _info->flags = RB_KERNEL; //release the frame + } + + bool ready(void){return _info->flags & RB_USER;} + + sptr get_new(void){ + if (fp_verbose) UHD_LOGV(always) << " make_recv_buff: " << get_size() << std::endl; + _info->flags = RB_USER_PROCESS; //claim the frame + return make_managed_buffer(this); + } + +private: + const void *get_buff(void) const{return _mem;} + size_t get_size(void) const{return _info->len;} + + void *_mem; + ring_buffer_info *_info; +}; + +/*********************************************************************** + * Reusable managed send buffer: + * - The buffer knows how to claim and release a frame. + **********************************************************************/ +class e100_mmap_zero_copy_msb : public managed_send_buffer{ +public: + e100_mmap_zero_copy_msb(void *mem, ring_buffer_info *info, size_t len, int fd): + _mem(mem), _info(info), _len(len), _fd(fd) { /* NOP */ } + + void commit(size_t len){ + if (_info->flags != RB_USER_PROCESS) return; + if (fp_verbose) UHD_LOGV(always) << "send buff: commit " << len << std::endl; + _info->len = len; + _info->flags = RB_USER; //release the frame + if (::write(_fd, NULL, 0) < 0){ //notifies the kernel + UHD_LOGV(rarely) << UHD_THROW_SITE_INFO("write error") << std::endl; + } + } + + bool ready(void){return _info->flags & RB_KERNEL;} + + sptr get_new(void){ + if (fp_verbose) UHD_LOGV(always) << " make_send_buff: " << get_size() << std::endl; + _info->flags = RB_USER_PROCESS; //claim the frame + return make_managed_buffer(this); + } + +private: + void *get_buff(void) const{return _mem;} + size_t get_size(void) const{return _len;} + + void *_mem; + ring_buffer_info *_info; + size_t _len; + int _fd; +}; + +/*********************************************************************** + * The zero copy interface implementation + **********************************************************************/ +class e100_mmap_zero_copy_impl : public zero_copy_if{ +public: + e100_mmap_zero_copy_impl(e100_ctrl::sptr iface): + _fd(iface->get_file_descriptor()), _recv_index(0), _send_index(0) + { + //get system sizes + iface->ioctl(USRP_E_GET_RB_INFO, &_rb_size); + size_t page_size = getpagesize(); + _frame_size = page_size/2; + + //calculate the memory size + _map_size = + (_rb_size.num_pages_rx_flags + _rb_size.num_pages_tx_flags) * page_size + + (_rb_size.num_rx_frames + _rb_size.num_tx_frames) * _frame_size; + + //print sizes summary + UHD_LOG + << "page_size: " << page_size << std::endl + << "frame_size: " << _frame_size << std::endl + << "num_pages_rx_flags: " << _rb_size.num_pages_rx_flags << std::endl + << "num_rx_frames: " << _rb_size.num_rx_frames << std::endl + << "num_pages_tx_flags: " << _rb_size.num_pages_tx_flags << std::endl + << "num_tx_frames: " << _rb_size.num_tx_frames << std::endl + << "map_size: " << _map_size << std::endl + ; + + //call mmap to get the memory + _mapped_mem = ::mmap( + NULL, _map_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0 + ); + UHD_ASSERT_THROW(_mapped_mem != MAP_FAILED); + + //calculate the memory offsets for info and buffers + size_t recv_info_off = 0; + size_t recv_buff_off = recv_info_off + (_rb_size.num_pages_rx_flags * page_size); + size_t send_info_off = recv_buff_off + (_rb_size.num_rx_frames * _frame_size); + size_t send_buff_off = send_info_off + (_rb_size.num_pages_tx_flags * page_size); + + //print offset summary + UHD_LOG + << "recv_info_off: " << recv_info_off << std::endl + << "recv_buff_off: " << recv_buff_off << std::endl + << "send_info_off: " << send_info_off << std::endl + << "send_buff_off: " << send_buff_off << std::endl + ; + + //pointers to sections in the mapped memory + ring_buffer_info (*recv_info)[], (*send_info)[]; + char *recv_buff, *send_buff; + + //set the internal pointers for info and buffers + typedef ring_buffer_info (*rbi_pta)[]; + char *rb_ptr = reinterpret_cast<char *>(_mapped_mem); + recv_info = reinterpret_cast<rbi_pta>(rb_ptr + recv_info_off); + recv_buff = rb_ptr + recv_buff_off; + send_info = reinterpret_cast<rbi_pta>(rb_ptr + send_info_off); + send_buff = rb_ptr + send_buff_off; + + //initialize the managed receive buffers + for (size_t i = 0; i < get_num_recv_frames(); i++){ + _mrb_pool.push_back(e100_mmap_zero_copy_mrb( + recv_buff + get_recv_frame_size()*i, (*recv_info) + i + )); + } + + //initialize the managed send buffers + for (size_t i = 0; i < get_num_recv_frames(); i++){ + _msb_pool.push_back(e100_mmap_zero_copy_msb( + send_buff + get_send_frame_size()*i, (*send_info) + i, + get_send_frame_size(), _fd + )); + } + } + + ~e100_mmap_zero_copy_impl(void){ + UHD_LOG << "cleanup: munmap" << std::endl; + ::munmap(_mapped_mem, _map_size); + } + + managed_recv_buffer::sptr get_recv_buff(double timeout){ + if (fp_verbose) UHD_LOGV(always) << "get_recv_buff: " << _recv_index << std::endl; + e100_mmap_zero_copy_mrb &mrb = _mrb_pool[_recv_index]; + + //poll/wait for a ready frame + if (not mrb.ready()){ + for (size_t i = 0; i < poll_breakout; i++){ + pollfd pfd; + pfd.fd = _fd; + pfd.events = POLLIN; + ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3/poll_breakout)); + if (fp_verbose) UHD_LOGV(always) << " POLLIN: " << poll_ret << std::endl; + if (poll_ret > 0) goto found_user_frame; //good poll, continue on + } + return managed_recv_buffer::sptr(); //timed-out for real + } found_user_frame: + + //increment the index for the next call + if (++_recv_index == get_num_recv_frames()) _recv_index = 0; + + //return the managed buffer for this frame + return mrb.get_new(); + } + + size_t get_num_recv_frames(void) const{ + return _rb_size.num_rx_frames; + } + + size_t get_recv_frame_size(void) const{ + return _frame_size; + } + + managed_send_buffer::sptr get_send_buff(double timeout){ + if (fp_verbose) UHD_LOGV(always) << "get_send_buff: " << _send_index << std::endl; + e100_mmap_zero_copy_msb &msb = _msb_pool[_send_index]; + + //poll/wait for a ready frame + if (not msb.ready()){ + pollfd pfd; + pfd.fd = _fd; + pfd.events = POLLOUT; + ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3)); + if (fp_verbose) UHD_LOGV(always) << " POLLOUT: " << poll_ret << std::endl; + if (poll_ret <= 0) return managed_send_buffer::sptr(); + } + + //increment the index for the next call + if (++_send_index == get_num_send_frames()) _send_index = 0; + + //return the managed buffer for this frame + return msb.get_new(); + } + + size_t get_num_send_frames(void) const{ + return _rb_size.num_tx_frames; + } + + size_t get_send_frame_size(void) const{ + return _frame_size; + } + +private: + //file descriptor for mmap + int _fd; + + //the mapped memory itself + void *_mapped_mem; + + //mapped memory sizes + usrp_e_ring_buffer_size_t _rb_size; + size_t _frame_size, _map_size; + + //re-usable managed buffers + std::vector<e100_mmap_zero_copy_mrb> _mrb_pool; + std::vector<e100_mmap_zero_copy_msb> _msb_pool; + + //indexes into sub-sections of mapped memory + size_t _recv_index, _send_index; +}; + +/*********************************************************************** + * The zero copy interface make function + **********************************************************************/ +zero_copy_if::sptr e100_make_mmap_zero_copy(e100_ctrl::sptr iface){ + return zero_copy_if::sptr(new e100_mmap_zero_copy_impl(iface)); +} diff --git a/host/lib/usrp/e100/e100_regs.hpp b/host/lib/usrp/e100/e100_regs.hpp new file mode 100644 index 000000000..75be2cfbe --- /dev/null +++ b/host/lib/usrp/e100/e100_regs.hpp @@ -0,0 +1,137 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +//////////////////////////////////////////////////////////////// +// +// Memory map for embedded wishbone bus +// +//////////////////////////////////////////////////////////////// + +// All addresses are byte addresses. All accesses are word (16-bit) accesses. +// This means that address bit 0 is usually 0. +// There are 11 bits of address for the control. + +#ifndef INCLUDED_E100_REGS_HPP +#define INCLUDED_E100_REGS_HPP + +///////////////////////////////////////////////////// +// Slave pointers + +#define E100_REG_SLAVE(n) ((n)<<7) + +///////////////////////////////////////////////////// +// Slave 0 -- Misc Regs + +#define E100_REG_MISC_BASE E100_REG_SLAVE(0) + +#define E100_REG_MISC_LED E100_REG_MISC_BASE + 0 +#define E100_REG_MISC_SW E100_REG_MISC_BASE + 2 +#define E100_REG_MISC_CGEN_CTRL E100_REG_MISC_BASE + 4 +#define E100_REG_MISC_CGEN_ST E100_REG_MISC_BASE + 6 +#define E100_REG_MISC_TEST E100_REG_MISC_BASE + 8 +#define E100_REG_MISC_RX_LEN E100_REG_MISC_BASE + 10 +#define E100_REG_MISC_TX_LEN E100_REG_MISC_BASE + 12 +#define E100_REG_MISC_XFER_RATE E100_REG_MISC_BASE + 14 + +///////////////////////////////////////////////////// +// Slave 1 -- UART +// CLKDIV is 16 bits, others are only 8 + +#define E100_REG_UART_BASE E100_REG_SLAVE(1) + +#define E100_REG_UART_CLKDIV E100_REG_UART_BASE + 0 +#define E100_REG_UART_TXLEVEL E100_REG_UART_BASE + 2 +#define E100_REG_UART_RXLEVEL E100_REG_UART_BASE + 4 +#define E100_REG_UART_TXCHAR E100_REG_UART_BASE + 6 +#define E100_REG_UART_RXCHAR E100_REG_UART_BASE + 8 + +///////////////////////////////////////////////////// +// Slave 2 -- SPI Core +//these are 32-bit registers mapped onto the 16-bit Wishbone bus. +//Using peek32/poke32 should allow transparent use of these registers. +#define E100_REG_SPI_BASE E100_REG_SLAVE(2) + +//spi slave constants +#define UE_SPI_SS_AD9522 (1 << 3) +#define UE_SPI_SS_AD9862 (1 << 2) +#define UE_SPI_SS_TX_DB (1 << 1) +#define UE_SPI_SS_RX_DB (1 << 0) + +//////////////////////////////////////////////// +// Slave 3 -- I2C Core + +#define E100_REG_I2C_BASE E100_REG_SLAVE(3) + +//////////////////////////////////////////////// +// Slave 5 -- Error messages buffer + +#define E100_REG_ERR_BUFF E100_REG_SLAVE(5) + +/////////////////////////////////////////////////// +// Slave 7 -- Readback Mux 32 + +#define E100_REG_RB_MUX_32_BASE E100_REG_SLAVE(7) + +#define E100_REG_RB_TIME_NOW_HI E100_REG_RB_MUX_32_BASE + 0 +#define E100_REG_RB_TIME_NOW_LO E100_REG_RB_MUX_32_BASE + 4 +#define E100_REG_RB_TIME_PPS_HI E100_REG_RB_MUX_32_BASE + 8 +#define E100_REG_RB_TIME_PPS_LO E100_REG_RB_MUX_32_BASE + 12 +#define E100_REG_RB_MISC_TEST32 E100_REG_RB_MUX_32_BASE + 16 +#define E100_REG_RB_ERR_STATUS E100_REG_RB_MUX_32_BASE + 20 +#define E100_REG_RB_COMPAT E100_REG_RB_MUX_32_BASE + 24 +#define E100_REG_RB_GPIO E100_REG_RB_MUX_32_BASE + 28 + +//////////////////////////////////////////////////// +// Slave 8 -- Settings Bus +// +// Output-only, no readback, 64 registers total +// Each register must be written 64 bits at a time +// First the address xxx_xx00 and then xxx_xx10 + +// 64 total regs in address space +#define UE_SR_RX_CTRL0 0 // 9 regs (+0 to +8) +#define UE_SR_RX_DSP0 10 // 4 regs (+0 to +3) +#define UE_SR_RX_CTRL1 16 // 9 regs (+0 to +8) +#define UE_SR_RX_DSP1 26 // 4 regs (+0 to +3) +#define UE_SR_ERR_CTRL 30 // 1 reg +#define UE_SR_TX_CTRL 32 // 4 regs (+0 to +3) +#define UE_SR_TX_DSP 38 // 3 regs (+0 to +2) + +#define UE_SR_TIME64 42 // 6 regs (+0 to +5) +#define UE_SR_RX_FRONT 48 // 5 regs (+0 to +4) +#define UE_SR_TX_FRONT 54 // 5 regs (+0 to +4) + +#define UE_SR_REG_TEST32 60 // 1 reg +#define UE_SR_CLEAR_FIFO 61 // 1 reg +#define UE_SR_GLOBAL_RESET 63 // 1 reg +#define UE_SR_USER_REGS 64 // 2 regs + +#define UE_SR_GPIO 128 + +#define E100_REG_SR_ADDR(n) (E100_REG_SLAVE(8) + (4*(n))) + +#define E100_REG_SR_MISC_TEST32 E100_REG_SR_ADDR(UE_SR_REG_TEST32) +#define E100_REG_SR_ERR_CTRL E100_REG_SR_ADDR(UE_SR_ERR_CTRL) + +///////////////////////////////////////////////// +// Magic reset regs +//////////////////////////////////////////////// +#define E100_REG_CLEAR_FIFO E100_REG_SR_ADDR(UE_SR_CLEAR_FIFO) +#define E100_REG_GLOBAL_RESET E100_REG_SR_ADDR(UE_SR_GLOBAL_RESET) + +#endif + diff --git a/host/lib/usrp/e100/fpga_downloader.cpp b/host/lib/usrp/e100/fpga_downloader.cpp new file mode 100644 index 000000000..7074c8299 --- /dev/null +++ b/host/lib/usrp/e100/fpga_downloader.cpp @@ -0,0 +1,272 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#ifdef UHD_DLL_EXPORTS +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#else //special case when this file is externally included +#include <stdexcept> +#include <iostream> +#define UHD_MSG(type) std::cout +namespace uhd{ + typedef std::runtime_error os_error; + typedef std::runtime_error io_error; +} +#endif + +#include <sstream> +#include <fstream> +#include <string> +#include <cstdlib> + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <linux/spi/spidev.h> + +/* + * Configuration connections + * + * CCK - MCSPI1_CLK + * DIN - MCSPI1_MOSI + * PROG_B - GPIO_175 - output (change mux) + * DONE - GPIO_173 - input (change mux) + * INIT_B - GPIO_114 - input (change mux) + * +*/ + +namespace usrp_e_fpga_downloader_utility{ + +const unsigned int PROG_B = 175; +const unsigned int DONE = 173; +const unsigned int INIT_B = 114; + +//static std::string bit_file = "safe_u1e.bin"; + +const int BUF_SIZE = 4096; + +enum gpio_direction {IN, OUT}; + +class gpio { + public: + + gpio(unsigned int gpio_num, gpio_direction pin_direction); + + bool get_value(); + void set_value(bool state); + + private: + + std::stringstream base_path; + std::fstream value_file; +}; + +class spidev { + public: + + spidev(std::string dev_name); + ~spidev(); + + void send(char *wbuf, char *rbuf, unsigned int nbytes); + + private: + + int fd; + +}; + +gpio::gpio(unsigned int gpio_num, gpio_direction pin_direction) +{ + std::fstream export_file; + + export_file.open("/sys/class/gpio/export", std::ios::out); + if (not export_file.is_open()) throw uhd::os_error( + "Failed to open gpio export file." + ); + + export_file << gpio_num << std::endl; + + base_path << "/sys/class/gpio/gpio" << gpio_num << std::flush; + + std::fstream direction_file; + std::string direction_file_name; + + if (gpio_num != 114) { + direction_file_name = base_path.str() + "/direction"; + + direction_file.open(direction_file_name.c_str()); + if (!direction_file.is_open()) throw uhd::os_error( + "Failed to open direction file." + ); + if (pin_direction == OUT) + direction_file << "out" << std::endl; + else + direction_file << "in" << std::endl; + } + + std::string value_file_name; + + value_file_name = base_path.str() + "/value"; + + value_file.open(value_file_name.c_str(), std::ios_base::in | std::ios_base::out); + if (!value_file.is_open()) throw uhd::os_error( + "Failed to open value file." + ); +} + +bool gpio::get_value() +{ + + std::string val; + + std::getline(value_file, val); + value_file.seekg(0); + + if (val == "0") + return false; + else if (val == "1") + return true; + else + throw uhd::os_error("Data read from value file|" + val + "|"); + + return false; +} + +void gpio::set_value(bool state) +{ + + if (state) + value_file << "1" << std::endl; + else + value_file << "0" << std::endl; +} + +static void prepare_fpga_for_configuration(gpio &prog, gpio &)//init) +{ + + prog.set_value(true); + prog.set_value(false); + prog.set_value(true); + +#if 0 + bool ready_to_program(false); + unsigned int count(0); + do { + ready_to_program = init.get_value(); + count++; + + sleep(1); + } while (count < 10 && !ready_to_program); + + if (count == 10) { + throw uhd::os_error("FPGA not ready for programming."); + } +#endif +} + +spidev::spidev(std::string fname) +{ + int ret; + int mode = 0; + int speed = 12000000; + int bits = 8; + + fd = open(fname.c_str(), O_RDWR); + + ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); + ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); + ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); +} + + +spidev::~spidev() +{ + close(fd); +} + +void spidev::send(char *buf, char *rbuf, unsigned int nbytes) +{ + int ret; + + struct spi_ioc_transfer tr; + tr.tx_buf = (unsigned long) buf; + tr.rx_buf = (unsigned long) rbuf; + tr.len = nbytes; + tr.delay_usecs = 0; + tr.speed_hz = 48000000; + tr.bits_per_word = 8; + + ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); + +} + +static void send_file_to_fpga(const std::string &file_name, gpio &error, gpio &done) +{ + std::ifstream bitstream; + + bitstream.open(file_name.c_str(), std::ios::binary); + if (!bitstream.is_open()) throw uhd::os_error( + "Coult not open the file: " + file_name + ); + + spidev spi("/dev/spidev1.0"); + char buf[BUF_SIZE]; + char rbuf[BUF_SIZE]; + + do { + bitstream.read(buf, BUF_SIZE); + spi.send(buf, rbuf, bitstream.gcount()); + + if (error.get_value()) + throw uhd::os_error("INIT_B went high, error occured."); + + if (!done.get_value()) + UHD_MSG(status) << "Configuration complete." << std::endl; + + } while (bitstream.gcount() == BUF_SIZE); +} + +}//namespace usrp_e_fpga_downloader_utility + +void e100_load_fpga(const std::string &bin_file){ + using namespace usrp_e_fpga_downloader_utility; + + gpio gpio_prog_b(PROG_B, OUT); + gpio gpio_init_b(INIT_B, IN); + gpio gpio_done (DONE, IN); + + UHD_MSG(status) << "Loading FPGA image: " << bin_file << "... " << std::flush; + +// if(std::system("/sbin/rmmod usrp_e") != 0){ +// UHD_MSG(warning) << "USRP-E100 FPGA downloader: could not unload usrp_e module" << std::endl; +// } + + prepare_fpga_for_configuration(gpio_prog_b, gpio_init_b); + + UHD_MSG(status) << "done = " << gpio_done.get_value() << std::endl; + + send_file_to_fpga(bin_file, gpio_init_b, gpio_done); + +// if(std::system("/sbin/modprobe usrp_e") != 0){ +// UHD_MSG(warning) << "USRP-E100 FPGA downloader: could not load usrp_e module" << std::endl; +// } + +} + diff --git a/host/lib/usrp/e100/include/linux/usrp_e.h b/host/lib/usrp/e100/include/linux/usrp_e.h new file mode 100644 index 000000000..37e9ee31a --- /dev/null +++ b/host/lib/usrp/e100/include/linux/usrp_e.h @@ -0,0 +1,60 @@ + +/* + * Copyright (C) 2010 Ettus Research, LLC + * + * Written by Philip Balister <philip@opensdr.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __USRP_E_H +#define __USRP_E_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +struct usrp_e_ctl16 { + __u32 offset; + __u32 count; + __u16 buf[20]; +}; + +struct usrp_e_ctl32 { + __u32 offset; + __u32 count; + __u32 buf[10]; +}; + +#define USRP_E_IOC_MAGIC 'u' +#define USRP_E_WRITE_CTL16 _IOW(USRP_E_IOC_MAGIC, 0x20, struct usrp_e_ctl16) +#define USRP_E_READ_CTL16 _IOWR(USRP_E_IOC_MAGIC, 0x21, struct usrp_e_ctl16) +#define USRP_E_WRITE_CTL32 _IOW(USRP_E_IOC_MAGIC, 0x22, struct usrp_e_ctl32) +#define USRP_E_READ_CTL32 _IOWR(USRP_E_IOC_MAGIC, 0x23, struct usrp_e_ctl32) +#define USRP_E_GET_RB_INFO _IOR(USRP_E_IOC_MAGIC, 0x27, struct usrp_e_ring_buffer_size_t) +#define USRP_E_GET_COMPAT_NUMBER _IO(USRP_E_IOC_MAGIC, 0x28) + +#define USRP_E_COMPAT_NUMBER 3 + +/* Flag defines */ +#define RB_USER (1<<0) +#define RB_KERNEL (1<<1) +#define RB_OVERRUN (1<<2) +#define RB_DMA_ACTIVE (1<<3) +#define RB_USER_PROCESS (1<<4) + +struct ring_buffer_info { + int flags; + int len; +}; + +struct usrp_e_ring_buffer_size_t { + int num_pages_rx_flags; + int num_rx_frames; + int num_pages_tx_flags; + int num_tx_frames; +}; + +#endif diff --git a/host/lib/usrp/e100/io_impl.cpp b/host/lib/usrp/e100/io_impl.cpp new file mode 100644 index 000000000..e9608125f --- /dev/null +++ b/host/lib/usrp/e100/io_impl.cpp @@ -0,0 +1,366 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "recv_packet_demuxer.hpp" +#include "validate_subdev_spec.hpp" +#include "async_packet_handler.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include <linux/usrp_e.h> //ioctl structures and constants +#include "e100_impl.hpp" +#include "e100_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread/thread.hpp> +#include <poll.h> //poll +#include <fcntl.h> //open, close +#include <sstream> +#include <fstream> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew of 1 + * - bounded buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct e100_impl::io_impl{ + io_impl(void): + false_alarm(0), async_msg_fifo(1000/*messages deep*/) + { /* NOP */ } + + double tick_rate; //set by update tick rate method + e100_ctrl::sptr iface; //so handle irq can peek and poke + void handle_irq(void); + size_t false_alarm; + //The data transport is listed first so that it is deconstructed last, + //which is after the states and booty which may hold managed buffers. + recv_packet_demuxer::sptr demuxer; + + //a pirate's life is the life for me! + void recv_pirate_loop( + spi_iface::sptr //keep a sptr to iface which shares gpio147 + ){ + //open the GPIO and set it up for an IRQ + std::ofstream edge_file("/sys/class/gpio/gpio147/edge"); + edge_file << "rising" << std::endl << std::flush; + edge_file.close(); + int fd = ::open("/sys/class/gpio/gpio147/value", O_RDONLY); + if (fd < 0) UHD_MSG(error) << "Unable to open GPIO for IRQ\n"; + + while (not boost::this_thread::interruption_requested()){ + pollfd pfd; + pfd.fd = fd; + pfd.events = POLLPRI | POLLERR; + ssize_t ret = ::poll(&pfd, 1, 100/*ms*/); + if (ret > 0) this->handle_irq(); + } + + //cleanup before thread exit + ::close(fd); + } + bounded_buffer<async_metadata_t> async_msg_fifo; + task::sptr pirate_task; +}; + +void e100_impl::io_impl::handle_irq(void){ + //check the status of the async msg buffer + const boost::uint32_t status = iface->peek32(E100_REG_RB_ERR_STATUS); + if ((status & 0x3) == 0){ //not done or error + //This could be a false-alarm because spi readback is mixed in. + //So we just sleep for a bit rather than interrupt continuously. + if (false_alarm++ > 3) boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + return; + } + false_alarm = 0; //its a real message, reset the count... + //std::cout << boost::format("status: 0x%x") % status << std::endl; + + //load the data struct and call the ioctl + usrp_e_ctl32 data; + data.offset = E100_REG_ERR_BUFF; + data.count = status >> 16; + iface->ioctl(USRP_E_READ_CTL32, &data); + //for (size_t i = 0; i < data.count; i++){ + //data.buf[i] = iface->peek32(E100_REG_ERR_BUFF + i*sizeof(boost::uint32_t)); + //std::cout << boost::format(" buff[%u] = 0x%08x\n") % i % data.buf[i]; + //} + + //unpack the vrt header and process below... + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = data.count; + try{vrt::if_hdr_unpack_le(data.buf, if_packet_info);} + catch(const std::exception &e){ + UHD_MSG(error) << "Error unpacking vrt header:\n" << e.what() << std::endl; + goto prepare; + } + + //handle a tx async report message + if (if_packet_info.sid == E100_TX_ASYNC_SID and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff(uhd::wtohx<boost::uint32_t>, metadata, if_packet_info, data.buf, tick_rate); + + //push the message onto the queue + async_msg_fifo.push_with_pop_on_full(metadata); + + //print some fastpath messages + standard_async_msg_prints(metadata); + } + + //prepare for the next round + prepare: + iface->poke32(E100_REG_SR_ERR_CTRL, 1 << 0); //clear + while ((iface->peek32(E100_REG_RB_ERR_STATUS) & (1 << 2)) == 0){} //wait for idle + iface->poke32(E100_REG_SR_ERR_CTRL, 1 << 1); //start +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void e100_impl::io_init(void){ + + //create new io impl + _io_impl = UHD_PIMPL_MAKE(io_impl, ()); + _io_impl->demuxer = recv_packet_demuxer::make(_data_transport, _rx_dsps.size(), E100_RX_SID_BASE); + _io_impl->iface = _fpga_ctrl; + + //clear fifo state machines + _fpga_ctrl->poke32(E100_REG_CLEAR_FIFO, 0); + + //allocate streamer weak ptrs containers + _rx_streamers.resize(_rx_dsps.size()); + _tx_streamers.resize(1/*known to be 1 dsp*/); + + //prepare the async msg buffer for incoming messages + _fpga_ctrl->poke32(E100_REG_SR_ERR_CTRL, 1 << 0); //clear + while ((_fpga_ctrl->peek32(E100_REG_RB_ERR_STATUS) & (1 << 2)) == 0){} //wait for idle + _fpga_ctrl->poke32(E100_REG_SR_ERR_CTRL, 1 << 1); //start + + //spawn a pirate, yarrr! + _io_impl->pirate_task = task::make(boost::bind( + &e100_impl::io_impl::recv_pirate_loop, _io_impl.get(), _aux_spi_iface + )); +} + +void e100_impl::update_tick_rate(const double rate){ + _io_impl->tick_rate = rate; + + //update the tick rate on all existing streamers -> thread safe + for (size_t i = 0; i < _rx_streamers.size(); i++){ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); + if (my_streamer.get() == NULL) continue; + my_streamer->set_tick_rate(rate); + } + for (size_t i = 0; i < _tx_streamers.size(); i++){ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[i].lock()); + if (my_streamer.get() == NULL) continue; + my_streamer->set_tick_rate(rate); + } +} + +void e100_impl::update_rx_samp_rate(const size_t dspno, const double rate){ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[dspno].lock()); + if (my_streamer.get() == NULL) return; + + my_streamer->set_samp_rate(rate); + const double adj = _rx_dsps[dspno]->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void e100_impl::update_tx_samp_rate(const size_t dspno, const double rate){ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[dspno].lock()); + if (my_streamer.get() == NULL) return; + + my_streamer->set_samp_rate(rate); + const double adj = _tx_dsp->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void e100_impl::update_rates(void){ + const fs_path mb_path = "/mboards/0"; + _tree->access<double>(mb_path / "tick_rate").update(); + + //and now that the tick rate is set, init the host rates to something + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ + _tree->access<double>(mb_path / "rx_dsps" / name / "rate" / "value").update(); + } + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ + _tree->access<double>(mb_path / "tx_dsps" / name / "rate" / "value").update(); + } +} + +void e100_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ + fs_path root = "/mboards/0/dboards"; + + //sanity checking + validate_subdev_spec(_tree, spec, "rx"); + + //setup mux for this spec + bool fe_swapped = false; + for (size_t i = 0; i < spec.size(); i++){ + const std::string conn = _tree->access<std::string>(root / spec[i].db_name / "rx_frontends" / spec[i].sd_name / "connection").get(); + if (i == 0 and (conn == "QI" or conn == "Q")) fe_swapped = true; + _rx_dsps[i]->set_mux(conn, fe_swapped); + } + _rx_fe->set_mux(fe_swapped); +} + +void e100_impl::update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ + fs_path root = "/mboards/0/dboards"; + + //sanity checking + validate_subdev_spec(_tree, spec, "tx"); + + //set the mux for this spec + const std::string conn = _tree->access<std::string>(root / spec[0].db_name / "tx_frontends" / spec[0].sd_name / "connection").get(); + _tx_fe->set_mux(conn); +} + +/*********************************************************************** + * Async Recv + **********************************************************************/ +bool e100_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +){ + boost::this_thread::disable_interruption di; //disable because the wait can throw + return _io_impl->async_msg_fifo.pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr e100_impl::get_rx_stream(const uhd::stream_args_t &args_){ + stream_args_t args = args_; + + //setup defaults for unspecified values + args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = _data_transport->get_recv_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + + //init some streamer stuff + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_unpacker(&vrt::if_hdr_unpack_le); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_le"; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //bind callbacks for the handler + for (size_t chan_i = 0; chan_i < args.channels.size(); chan_i++){ + const size_t dsp = args.channels[chan_i]; + _rx_dsps[dsp]->set_nsamps_per_packet(spp); //seems to be a good place to set this + _rx_dsps[dsp]->setup(args); + my_streamer->set_xport_chan_get_buff(chan_i, boost::bind( + &recv_packet_demuxer::get_recv_buff, _io_impl->demuxer, dsp, _1 + ), true /*flush*/); + my_streamer->set_overflow_handler(chan_i, boost::bind( + &rx_dsp_core_200::handle_overflow, _rx_dsps[dsp] + )); + _rx_streamers[dsp] = my_streamer; //store weak pointer + } + + //sets all tick and samp rates on this streamer + this->update_rates(); + + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr e100_impl::get_tx_stream(const uhd::stream_args_t &args_){ + stream_args_t args = args_; + + //setup defaults for unspecified values + args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().sid) //no stream id ever used + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + static const size_t bpp = _data_transport->get_send_frame_size() - hdr_size; + const size_t spp = bpp/convert::get_bytes_per_item(args.otw_format); + + //make the new streamer given the samples per packet + boost::shared_ptr<sph::send_packet_streamer> my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + + //init some streamer stuff + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_packer(&vrt::if_hdr_pack_le); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_le"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //bind callbacks for the handler + for (size_t chan_i = 0; chan_i < args.channels.size(); chan_i++){ + const size_t dsp = args.channels[chan_i]; + UHD_ASSERT_THROW(dsp == 0); //always 0 + _tx_dsp->setup(args); + my_streamer->set_xport_chan_get_buff(chan_i, boost::bind( + &zero_copy_if::get_send_buff, _data_transport, _1 + )); + _tx_streamers[dsp] = my_streamer; //store weak pointer + } + + //sets all tick and samp rates on this streamer + this->update_rates(); + + return my_streamer; +} diff --git a/host/lib/usrp/gps_ctrl.cpp b/host/lib/usrp/gps_ctrl.cpp new file mode 100644 index 000000000..45fa1f39e --- /dev/null +++ b/host/lib/usrp/gps_ctrl.cpp @@ -0,0 +1,275 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/utils/msg.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/tokenizer.hpp> +#include <boost/format.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) + */ + +class gps_ctrl_impl : public gps_ctrl{ +public: + gps_ctrl_impl(uart_iface::sptr uart){ + _uart = uart; + + std::string reply; + bool i_heard_some_nmea = false, i_heard_something_weird = false; + gps_type = GPS_TYPE_NONE; + + //first we look for a Jackson Labs Firefly (since that's what we provide...) + _flush(); //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 + + //wait for _send(...) to return + sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); + + //then we loop until we either timeout, or until we get a response that indicates we're a JL device + const boost::system_time comm_timeout = boost::get_system_time() + milliseconds(GPS_COMM_TIMEOUT_MS); + while(boost::get_system_time() < comm_timeout) { + 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 + sleep(milliseconds(GPS_TIMEOUT_DELAY_MS)); + } + + if((i_heard_some_nmea) && (gps_type != GPS_TYPE_JACKSON_LABS)) gps_type = GPS_TYPE_GENERIC_NMEA; + + if((gps_type == GPS_TYPE_NONE) && i_heard_something_weird) { + UHD_MSG(error) << "GPS invalid reply \"" << reply << "\", assuming none available" << std::endl; + } + + switch(gps_type) { + case GPS_TYPE_JACKSON_LABS: + UHD_MSG(status) << "Found a Jackson Labs GPS" << std::endl; + init_firefly(); + break; + + case GPS_TYPE_GENERIC_NMEA: + if(gps_type == GPS_TYPE_GENERIC_NMEA) UHD_MSG(status) << "Found a generic NMEA GPS device" << std::endl; + break; + + case GPS_TYPE_NONE: + default: + 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; + } + + 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 { + throw uhd::value_error("gps ctrl get_sensor unknown key: " + key); + } + } + +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"; + return std::string(); + } + + _flush(); //flush all input before waiting for a message + + const boost::system_time comm_timeout = boost::get_system_time() + milliseconds(GPS_COMM_TIMEOUT_MS); + while(boost::get_system_time() < comm_timeout) { + reply = _recv(); + if(reply.substr(0, 6) == msgtype) + return reply; + boost::this_thread::sleep(milliseconds(GPS_TIMEOUT_DELAY_MS)); + } + throw uhd::value_error(str(boost::format("get_nmea(): no %s message found") % msgtype)); + 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<boost::escaped_list_separator<char> > tok(sentence); + std::vector<std::string> toked; + tok.assign(sentence); //this can throw + toked.assign(tok.begin(), tok.end()); + + if(toked.size() <= offset) { + throw uhd::value_error(str(boost::format("Invalid response \"%s\"") % sentence)); + } + return toked[offset]; + } + + ptime get_time(void) { + int error_cnt = 0; + ptime gps_time; + while(error_cnt < 3) { + try { + 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) { + throw uhd::value_error(str(boost::format("Invalid response \"%s\"") % reply)); + } + + //just trust me on this one + gps_time = 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))) + ); + return gps_time; + + } catch(std::exception &e) { + UHD_MSG(warning) << "get_time: " << e.what(); + error_cnt++; + } + } + throw uhd::value_error("Timeout after no valid message found"); + + return gps_time; //keep gcc from complaining + } + + time_t get_epoch_time(void) { + return (get_time() - from_time_t(0)).total_seconds(); + } + + bool gps_detected(void) { + return (gps_type != GPS_TYPE_NONE); + } + + bool locked(void) { + int error_cnt = 0; + while(error_cnt < 3) { + try { + std::string reply = get_nmea("GPGGA"); + if(reply.size() <= 1) return false; + + return (get_token(reply, 6) != "0"); + } catch(std::exception &e) { + UHD_MSG(warning) << "locked: " << e.what(); + error_cnt++; + } + } + throw uhd::value_error("Timeout after no valid message found"); + return false; + } + + uart_iface::sptr _uart; + + void _flush(void){ + while (not _uart->read_uart(0.0).empty()){ + //NOP + } + } + + std::string _recv(void){ + return _uart->read_uart(GPS_TIMEOUT_DELAY_MS/1000.); + } + + void _send(const std::string &buf){ + return _uart->write_uart(buf); + } + + enum { + GPS_TYPE_JACKSON_LABS, + GPS_TYPE_GENERIC_NMEA, + GPS_TYPE_NONE + } gps_type; + + static const int GPS_COMM_TIMEOUT_MS = 1500; + static const int GPS_TIMEOUT_DELAY_MS = 200; + static const int FIREFLY_STUPID_DELAY_MS = 200; +}; + +/*********************************************************************** + * Public make function for the GPS control + **********************************************************************/ +gps_ctrl::sptr gps_ctrl::make(uart_iface::sptr uart){ + return sptr(new gps_ctrl_impl(uart)); +} diff --git a/host/lib/usrp/mboard_eeprom.cpp b/host/lib/usrp/mboard_eeprom.cpp new file mode 100644 index 000000000..785d30296 --- /dev/null +++ b/host/lib/usrp/mboard_eeprom.cpp @@ -0,0 +1,445 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/mac_addr.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/foreach.hpp> +#include <algorithm> +#include <iostream> +#include <cstddef> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const size_t SERIAL_LEN = 9; +static const size_t NAME_MAX_LEN = 32 - SERIAL_LEN; + +/*********************************************************************** + * Utility functions + **********************************************************************/ + +//! A wrapper around std::copy that takes ranges instead of iterators. +template<typename RangeSrc, typename RangeDst> inline +void byte_copy(const RangeSrc &src, RangeDst &dst){ + std::copy(boost::begin(src), boost::end(src), boost::begin(dst)); +} + +//! create a string from a byte vector, return empty if invalid ascii +static const std::string bytes_to_string(const byte_vector_t &bytes){ + std::string out; + BOOST_FOREACH(boost::uint8_t byte, bytes){ + if (byte < 32 or byte > 127) return out; + out += byte; + } + return out; +} + +//! create a byte vector from a string, null terminate unless max length +static const byte_vector_t string_to_bytes(const std::string &string, size_t max_length){ + byte_vector_t bytes; + for (size_t i = 0; i < std::min(string.size(), max_length); i++){ + bytes.push_back(string[i]); + } + if (bytes.size() < max_length - 1) bytes.push_back('\0'); + return bytes; +} + +//! convert a string to a byte vector to write to eeprom +static byte_vector_t string_to_uint16_bytes(const std::string &num_str){ + const boost::uint16_t num = boost::lexical_cast<boost::uint16_t>(num_str); + const byte_vector_t lsb_msb = boost::assign::list_of + (boost::uint8_t(num >> 0))(boost::uint8_t(num >> 8)); + return lsb_msb; +} + +//! convert a byte vector read from eeprom to a string +static std::string uint16_bytes_to_string(const byte_vector_t &bytes){ + const boost::uint16_t num = (boost::uint16_t(bytes.at(0)) << 0) | (boost::uint16_t(bytes.at(1)) << 8); + return (num == 0 or num == 0xffff)? "" : boost::lexical_cast<std::string>(num); +} + +/*********************************************************************** + * Implementation of N100 load/store + **********************************************************************/ +static const boost::uint8_t N100_EEPROM_ADDR = 0x50; + +static const uhd::dict<std::string, boost::uint8_t> USRP_N100_OFFSETS = boost::assign::map_list_of + ("hardware", 0x00) + ("mac-addr", 0x02) + ("ip-addr", 0x0C) + //leave space here for other addresses (perhaps) + ("revision", 0x12) + ("product", 0x14) + ("gpsdo", 0x17) + ("serial", 0x18) + ("name", 0x18 + SERIAL_LEN) +; + +enum n200_gpsdo_type{ + N200_GPSDO_NONE = 0, + N200_GPSDO_INTERNAL = 1, + N200_GPSDO_ONBOARD = 2 +}; + +static void load_n100(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + //extract the hardware number + mb_eeprom["hardware"] = uint16_bytes_to_string( + iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["hardware"], 2) + ); + + //extract the revision number + mb_eeprom["revision"] = uint16_bytes_to_string( + iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["revision"], 2) + ); + + //extract the product code + mb_eeprom["product"] = uint16_bytes_to_string( + iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["product"], 2) + ); + + //extract the addresses + mb_eeprom["mac-addr"] = mac_addr_t::from_bytes(iface.read_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["mac-addr"], 6 + )).to_string(); + + boost::asio::ip::address_v4::bytes_type ip_addr_bytes; + byte_copy(iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["ip-addr"], 4), ip_addr_bytes); + mb_eeprom["ip-addr"] = boost::asio::ip::address_v4(ip_addr_bytes).to_string(); + + //gpsdo capabilities + boost::uint8_t gpsdo_byte = iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["gpsdo"], 1).at(0); + switch(n200_gpsdo_type(gpsdo_byte)){ + case N200_GPSDO_INTERNAL: mb_eeprom["gpsdo"] = "internal"; break; + case N200_GPSDO_ONBOARD: mb_eeprom["gpsdo"] = "onboard"; break; + default: mb_eeprom["gpsdo"] = "none"; + } + + //extract the serial + mb_eeprom["serial"] = bytes_to_string(iface.read_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["serial"], SERIAL_LEN + )); + + //extract the name + mb_eeprom["name"] = bytes_to_string(iface.read_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["name"], NAME_MAX_LEN + )); + + //Empty serial correction: use the mac address to determine serial. + //Older usrp2 models don't have a serial burned into EEPROM. + //The lower mac address bits will function as the serial number. + if (mb_eeprom["serial"].empty()){ + byte_vector_t mac_addr_bytes = mac_addr_t::from_string(mb_eeprom["mac-addr"]).to_bytes(); + unsigned serial = mac_addr_bytes.at(5) | (unsigned(mac_addr_bytes.at(4) & 0x0f) << 8); + mb_eeprom["serial"] = boost::lexical_cast<std::string>(serial); + } +} + +static void store_n100(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + //parse the revision number + if (mb_eeprom.has_key("hardware")) iface.write_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["hardware"], + string_to_uint16_bytes(mb_eeprom["hardware"]) + ); + + //parse the revision number + if (mb_eeprom.has_key("revision")) iface.write_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["revision"], + string_to_uint16_bytes(mb_eeprom["revision"]) + ); + + //parse the product code + if (mb_eeprom.has_key("product")) iface.write_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["product"], + string_to_uint16_bytes(mb_eeprom["product"]) + ); + + //store the addresses + if (mb_eeprom.has_key("mac-addr")) iface.write_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["mac-addr"], + mac_addr_t::from_string(mb_eeprom["mac-addr"]).to_bytes() + ); + + if (mb_eeprom.has_key("ip-addr")){ + byte_vector_t ip_addr_bytes(4); + byte_copy(boost::asio::ip::address_v4::from_string(mb_eeprom["ip-addr"]).to_bytes(), ip_addr_bytes); + iface.write_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["ip-addr"], ip_addr_bytes); + } + + //gpsdo capabilities + if (mb_eeprom.has_key("gpsdo")){ + boost::uint8_t gpsdo_byte = N200_GPSDO_NONE; + if (mb_eeprom["gpsdo"] == "internal") gpsdo_byte = N200_GPSDO_INTERNAL; + if (mb_eeprom["gpsdo"] == "onboard") gpsdo_byte = N200_GPSDO_ONBOARD; + iface.write_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["gpsdo"], byte_vector_t(1, gpsdo_byte)); + } + + //store the serial + if (mb_eeprom.has_key("serial")) iface.write_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["serial"], + string_to_bytes(mb_eeprom["serial"], SERIAL_LEN) + ); + + //store the name + if (mb_eeprom.has_key("name")) iface.write_eeprom( + N100_EEPROM_ADDR, USRP_N100_OFFSETS["name"], + string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) + ); +} + +/*********************************************************************** + * Implementation of B000 load/store + **********************************************************************/ +static const boost::uint8_t B000_EEPROM_ADDR = 0x50; +static const size_t B000_SERIAL_LEN = 8; + +//use char array so we dont need to attribute packed +struct b000_eeprom_map{ + unsigned char _r[221]; + unsigned char mcr[4]; + unsigned char name[NAME_MAX_LEN]; + unsigned char serial[B000_SERIAL_LEN]; +}; + +static void load_b000(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + //extract the serial + mb_eeprom["serial"] = bytes_to_string(iface.read_eeprom( + B000_EEPROM_ADDR, offsetof(b000_eeprom_map, serial), B000_SERIAL_LEN + )); + + //extract the name + mb_eeprom["name"] = bytes_to_string(iface.read_eeprom( + B000_EEPROM_ADDR, offsetof(b000_eeprom_map, name), NAME_MAX_LEN + )); + + //extract master clock rate as a 32-bit uint in Hz + boost::uint32_t master_clock_rate; + const byte_vector_t rate_bytes = iface.read_eeprom( + B000_EEPROM_ADDR, offsetof(b000_eeprom_map, mcr), sizeof(master_clock_rate) + ); + std::copy( + rate_bytes.begin(), rate_bytes.end(), //input + reinterpret_cast<boost::uint8_t *>(&master_clock_rate) //output + ); + master_clock_rate = ntohl(master_clock_rate); + if (master_clock_rate > 1e6 and master_clock_rate < 1e9){ + mb_eeprom["mcr"] = boost::lexical_cast<std::string>(master_clock_rate); + } + else mb_eeprom["mcr"] = ""; +} + +static void store_b000(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + //store the serial + if (mb_eeprom.has_key("serial")) iface.write_eeprom( + B000_EEPROM_ADDR, offsetof(b000_eeprom_map, serial), + string_to_bytes(mb_eeprom["serial"], B000_SERIAL_LEN) + ); + + //store the name + if (mb_eeprom.has_key("name")) iface.write_eeprom( + B000_EEPROM_ADDR, offsetof(b000_eeprom_map, name), + string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) + ); + + //store the master clock rate as a 32-bit uint in Hz + if (mb_eeprom.has_key("mcr")){ + boost::uint32_t master_clock_rate = boost::uint32_t(boost::lexical_cast<double>(mb_eeprom["mcr"])); + master_clock_rate = htonl(master_clock_rate); + const byte_vector_t rate_bytes( + reinterpret_cast<const boost::uint8_t *>(&master_clock_rate), + reinterpret_cast<const boost::uint8_t *>(&master_clock_rate) + sizeof(master_clock_rate) + ); + iface.write_eeprom( + B000_EEPROM_ADDR, offsetof(b000_eeprom_map, mcr), rate_bytes + ); + } +} + +/*********************************************************************** + * Implementation of B100 load/store + **********************************************************************/ +static const boost::uint8_t B100_EEPROM_ADDR = 0x50; + +//use char array so we dont need to attribute packed +struct b100_eeprom_map{ + unsigned char _r[220]; + unsigned char revision[2]; + unsigned char product[2]; + unsigned char name[NAME_MAX_LEN]; + unsigned char serial[SERIAL_LEN]; +}; + +static void load_b100(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + //extract the revision number + mb_eeprom["revision"] = uint16_bytes_to_string( + iface.read_eeprom(B100_EEPROM_ADDR, offsetof(b100_eeprom_map, revision), 2) + ); + + //extract the product code + mb_eeprom["product"] = uint16_bytes_to_string( + iface.read_eeprom(B100_EEPROM_ADDR, offsetof(b100_eeprom_map, product), 2) + ); + + //extract the serial + mb_eeprom["serial"] = bytes_to_string(iface.read_eeprom( + B100_EEPROM_ADDR, offsetof(b100_eeprom_map, serial), SERIAL_LEN + )); + + //extract the name + mb_eeprom["name"] = bytes_to_string(iface.read_eeprom( + B100_EEPROM_ADDR, offsetof(b100_eeprom_map, name), NAME_MAX_LEN + )); +} + +static void store_b100(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + //parse the revision number + if (mb_eeprom.has_key("revision")) iface.write_eeprom( + B100_EEPROM_ADDR, offsetof(b100_eeprom_map, revision), + string_to_uint16_bytes(mb_eeprom["revision"]) + ); + + //parse the product code + if (mb_eeprom.has_key("product")) iface.write_eeprom( + B100_EEPROM_ADDR, offsetof(b100_eeprom_map, product), + string_to_uint16_bytes(mb_eeprom["product"]) + ); + + //store the serial + if (mb_eeprom.has_key("serial")) iface.write_eeprom( + B100_EEPROM_ADDR, offsetof(b100_eeprom_map, serial), + string_to_bytes(mb_eeprom["serial"], SERIAL_LEN) + ); + + //store the name + if (mb_eeprom.has_key("name")) iface.write_eeprom( + B100_EEPROM_ADDR, offsetof(b100_eeprom_map, name), + string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) + ); +} + +/*********************************************************************** + * Implementation of E100 load/store + **********************************************************************/ +static const boost::uint8_t E100_EEPROM_ADDR = 0x51; + +struct e100_eeprom_map{ + boost::uint16_t vendor; + boost::uint16_t device; + unsigned char revision; + unsigned char content; + unsigned char model[8]; + unsigned char env_var[16]; + unsigned char env_setting[64]; + unsigned char serial[10]; + unsigned char name[NAME_MAX_LEN]; +}; + +template <typename T> static const byte_vector_t to_bytes(const T &item){ + return byte_vector_t( + reinterpret_cast<const byte_vector_t::value_type *>(&item), + reinterpret_cast<const byte_vector_t::value_type *>(&item)+sizeof(item) + ); +} + +#define sizeof_member(struct_name, member_name) \ + sizeof(reinterpret_cast<struct_name*>(NULL)->member_name) + +static void load_e100(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + const size_t num_bytes = offsetof(e100_eeprom_map, model); + byte_vector_t map_bytes = iface.read_eeprom(E100_EEPROM_ADDR, 0, num_bytes); + e100_eeprom_map map; std::memcpy(&map, &map_bytes[0], map_bytes.size()); + + mb_eeprom["vendor"] = boost::lexical_cast<std::string>(uhd::ntohx(map.vendor)); + mb_eeprom["device"] = boost::lexical_cast<std::string>(uhd::ntohx(map.device)); + mb_eeprom["revision"] = boost::lexical_cast<std::string>(unsigned(map.revision)); + mb_eeprom["content"] = boost::lexical_cast<std::string>(unsigned(map.content)); + + #define load_e100_string_xx(key) mb_eeprom[#key] = bytes_to_string(iface.read_eeprom( \ + E100_EEPROM_ADDR, offsetof(e100_eeprom_map, key), sizeof_member(e100_eeprom_map, key) \ + )); + + load_e100_string_xx(model); + load_e100_string_xx(env_var); + load_e100_string_xx(env_setting); + load_e100_string_xx(serial); + load_e100_string_xx(name); +} + +static void store_e100(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + + if (mb_eeprom.has_key("vendor")) iface.write_eeprom( + E100_EEPROM_ADDR, offsetof(e100_eeprom_map, vendor), + to_bytes(uhd::htonx(boost::lexical_cast<boost::uint16_t>(mb_eeprom["vendor"]))) + ); + + if (mb_eeprom.has_key("device")) iface.write_eeprom( + E100_EEPROM_ADDR, offsetof(e100_eeprom_map, device), + to_bytes(uhd::htonx(boost::lexical_cast<boost::uint16_t>(mb_eeprom["device"]))) + ); + + if (mb_eeprom.has_key("revision")) iface.write_eeprom( + E100_EEPROM_ADDR, offsetof(e100_eeprom_map, revision), + byte_vector_t(1, boost::lexical_cast<unsigned>(mb_eeprom["revision"])) + ); + + if (mb_eeprom.has_key("content")) iface.write_eeprom( + E100_EEPROM_ADDR, offsetof(e100_eeprom_map, content), + byte_vector_t(1, boost::lexical_cast<unsigned>(mb_eeprom["content"])) + ); + + #define store_e100_string_xx(key) if (mb_eeprom.has_key(#key)) iface.write_eeprom( \ + E100_EEPROM_ADDR, offsetof(e100_eeprom_map, key), \ + string_to_bytes(mb_eeprom[#key], sizeof_member(e100_eeprom_map, key)) \ + ); + + store_e100_string_xx(model); + store_e100_string_xx(env_var); + store_e100_string_xx(env_setting); + store_e100_string_xx(serial); + store_e100_string_xx(name); +} + +/*********************************************************************** + * Implementation of mboard eeprom + **********************************************************************/ +mboard_eeprom_t::mboard_eeprom_t(void){ + /* NOP */ +} + +mboard_eeprom_t::mboard_eeprom_t(i2c_iface &iface, map_type map){ + switch(map){ + case MAP_N100: load_n100(*this, iface); break; + case MAP_B000: load_b000(*this, iface); break; + case MAP_B100: load_b100(*this, iface); break; + case MAP_E100: load_e100(*this, iface); break; + } +} + +void mboard_eeprom_t::commit(i2c_iface &iface, map_type map) const{ + switch(map){ + case MAP_N100: store_n100(*this, iface); break; + case MAP_B000: store_b000(*this, iface); break; + case MAP_B100: store_b100(*this, iface); break; + case MAP_E100: store_e100(*this, iface); break; + } +} diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp new file mode 100644 index 000000000..c0f16d32c --- /dev/null +++ b/host/lib/usrp/multi_usrp.cpp @@ -0,0 +1,849 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/property_tree.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/gain_group.hpp> +#include <boost/thread.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; + +const std::string multi_usrp::ALL_GAINS = ""; + +/*********************************************************************** + * Helper methods + **********************************************************************/ +static void do_samp_rate_warning_message( + double target_rate, + double actual_rate, + const std::string &xx +){ + static const double max_allowed_error = 1.0; //Sps + if (std::abs(target_rate - actual_rate) > max_allowed_error){ + UHD_MSG(warning) << boost::format( + "The hardware does not support the requested %s sample rate:\n" + "Target sample rate: %f MSps\n" + "Actual sample rate: %f MSps\n" + ) % xx % (target_rate/1e6) % (actual_rate/1e6); + } +} + +static void do_tune_freq_warning_message( + const tune_request_t &tune_req, + double actual_freq, + const std::string &xx +){ + //forget the warning when manual policy + if (tune_req.dsp_freq_policy == tune_request_t::POLICY_MANUAL) return; + if (tune_req.rf_freq_policy == tune_request_t::POLICY_MANUAL) return; + + const double target_freq = tune_req.target_freq; + static const double max_allowed_error = 1.0; //Hz + if (std::abs(target_freq - actual_freq) > max_allowed_error){ + UHD_MSG(warning) << boost::format( + "The hardware does not support the requested %s frequency:\n" + "Target frequency: %f MHz\n" + "Actual frequency: %f MHz\n" + ) % xx % (target_freq/1e6) % (actual_freq/1e6); + } +} + +static meta_range_t make_overall_tune_range( + const meta_range_t &fe_range, + const meta_range_t &dsp_range, + const double bw +){ + meta_range_t range; + BOOST_FOREACH(const range_t &sub_range, fe_range){ + range.push_back(range_t( + sub_range.start() + std::max(dsp_range.start(), -bw), + sub_range.stop() + std::min(dsp_range.stop(), bw), + dsp_range.step() + )); + } + return range; +} + +/*********************************************************************** + * Gain helper functions + **********************************************************************/ +static double get_gain_value(property_tree::sptr subtree){ + return subtree->access<double>("value").get(); +} + +static void set_gain_value(property_tree::sptr subtree, const double gain){ + subtree->access<double>("value").set(gain); +} + +static meta_range_t get_gain_range(property_tree::sptr subtree){ + return subtree->access<meta_range_t>("range").get(); +} + +static gain_fcns_t make_gain_fcns_from_subtree(property_tree::sptr subtree){ + gain_fcns_t gain_fcns; + gain_fcns.get_range = boost::bind(&get_gain_range, subtree); + gain_fcns.get_value = boost::bind(&get_gain_value, subtree); + gain_fcns.set_value = boost::bind(&set_gain_value, subtree, _1); + return gain_fcns; +} + +/*********************************************************************** + * Tune Helper Functions + **********************************************************************/ +static const double RX_SIGN = +1.0; +static const double TX_SIGN = -1.0; + +static tune_result_t tune_xx_subdev_and_dsp( + const double xx_sign, + property_tree::sptr dsp_subtree, + property_tree::sptr rf_fe_subtree, + const tune_request_t &tune_request +){ + //------------------------------------------------------------------ + //-- calculate the LO offset, only used with automatic policy + //------------------------------------------------------------------ + double lo_offset = 0.0; + if (rf_fe_subtree->access<bool>("use_lo_offset").get()){ + //If the local oscillator will be in the passband, use an offset. + //But constrain the LO offset by the width of the filter bandwidth. + const double rate = dsp_subtree->access<double>("rate/value").get(); + const double bw = rf_fe_subtree->access<double>("bandwidth/value").get(); + if (bw > rate) lo_offset = std::min((bw - rate)/2, rate/2); + } + + //------------------------------------------------------------------ + //-- set the RF frequency depending upon the policy + //------------------------------------------------------------------ + double target_rf_freq = 0.0; + switch (tune_request.rf_freq_policy){ + case tune_request_t::POLICY_AUTO: + target_rf_freq = tune_request.target_freq + lo_offset; + rf_fe_subtree->access<double>("freq/value").set(target_rf_freq); + break; + + case tune_request_t::POLICY_MANUAL: + target_rf_freq = tune_request.rf_freq; + rf_fe_subtree->access<double>("freq/value").set(target_rf_freq); + break; + + case tune_request_t::POLICY_NONE: break; //does not set + } + const double actual_rf_freq = rf_fe_subtree->access<double>("freq/value").get(); + + //------------------------------------------------------------------ + //-- calculate the dsp freq, only used with automatic policy + //------------------------------------------------------------------ + double target_dsp_freq = actual_rf_freq - tune_request.target_freq; + + //invert the sign on the dsp freq for transmit + target_dsp_freq *= xx_sign; + + //------------------------------------------------------------------ + //-- set the dsp frequency depending upon the dsp frequency policy + //------------------------------------------------------------------ + switch (tune_request.dsp_freq_policy){ + case tune_request_t::POLICY_AUTO: + dsp_subtree->access<double>("freq/value").set(target_dsp_freq); + break; + + case tune_request_t::POLICY_MANUAL: + target_dsp_freq = tune_request.dsp_freq; + dsp_subtree->access<double>("freq/value").set(target_dsp_freq); + break; + + case tune_request_t::POLICY_NONE: break; //does not set + } + const double actual_dsp_freq = dsp_subtree->access<double>("freq/value").get(); + + //------------------------------------------------------------------ + //-- load and return the tune result + //------------------------------------------------------------------ + tune_result_t tune_result; + tune_result.target_rf_freq = target_rf_freq; + tune_result.actual_rf_freq = actual_rf_freq; + tune_result.target_dsp_freq = target_dsp_freq; + tune_result.actual_dsp_freq = actual_dsp_freq; + return tune_result; +} + +static double derive_freq_from_xx_subdev_and_dsp( + const double xx_sign, + property_tree::sptr dsp_subtree, + property_tree::sptr rf_fe_subtree +){ + //extract actual dsp and IF frequencies + const double actual_rf_freq = rf_fe_subtree->access<double>("freq/value").get(); + const double actual_dsp_freq = dsp_subtree->access<double>("freq/value").get(); + + //invert the sign on the dsp freq for transmit + return actual_rf_freq - actual_dsp_freq * xx_sign; +} + +/*********************************************************************** + * Multi USRP Implementation + **********************************************************************/ +class multi_usrp_impl : public multi_usrp{ +public: + multi_usrp_impl(const device_addr_t &addr){ + _dev = device::make(addr); + _tree = _dev->get_tree(); + } + + device::sptr get_device(void){ + return _dev; + } + + /******************************************************************* + * Mboard methods + ******************************************************************/ + void set_master_clock_rate(double rate, size_t mboard){ + if (mboard != ALL_MBOARDS){ + _tree->access<double>(mb_root(mboard) / "tick_rate").set(rate); + return; + } + for (size_t m = 0; m < get_num_mboards(); m++){ + set_master_clock_rate(rate, m); + } + } + + double get_master_clock_rate(size_t mboard){ + return _tree->access<double>(mb_root(mboard) / "tick_rate").get(); + } + + std::string get_pp_string(void){ + std::string buff = str(boost::format( + "%s USRP:\n" + " Device: %s\n" + ) + % ((get_num_mboards() > 1)? "Multi" : "Single") + % (_tree->access<std::string>("/name").get()) + ); + for (size_t m = 0; m < get_num_mboards(); m++){ + buff += str(boost::format( + " Mboard %d: %s\n" + ) % m + % (_tree->access<std::string>(mb_root(m) / "name").get()) + ); + } + + //----------- rx side of life ---------------------------------- + for (size_t m = 0, chan = 0; m < get_num_mboards(); m++){ + for (; chan < (m + 1)*get_rx_subdev_spec(m).size(); chan++){ + buff += str(boost::format( + " RX Channel: %u\n" + " RX DSP: %s\n" + " RX Dboard: %s\n" + " RX Subdev: %s\n" + ) % chan + % rx_dsp_root(chan).leaf() + % rx_rf_fe_root(chan).branch_path().branch_path().leaf() + % (_tree->access<std::string>(rx_rf_fe_root(chan) / "name").get()) + ); + } + } + + //----------- tx side of life ---------------------------------- + for (size_t m = 0, chan = 0; m < get_num_mboards(); m++){ + for (; chan < (m + 1)*get_tx_subdev_spec(m).size(); chan++){ + buff += str(boost::format( + " TX Channel: %u\n" + " TX DSP: %s\n" + " TX Dboard: %s\n" + " TX Subdev: %s\n" + ) % chan + % tx_dsp_root(chan).leaf() + % tx_rf_fe_root(chan).branch_path().branch_path().leaf() + % (_tree->access<std::string>(tx_rf_fe_root(chan) / "name").get()) + ); + } + } + + return buff; + } + + std::string get_mboard_name(size_t mboard){ + return _tree->access<std::string>(mb_root(mboard) / "name").get(); + } + + time_spec_t get_time_now(size_t mboard = 0){ + return _tree->access<time_spec_t>(mb_root(mboard) / "time/now").get(); + } + + time_spec_t get_time_last_pps(size_t mboard = 0){ + return _tree->access<time_spec_t>(mb_root(mboard) / "time/pps").get(); + } + + void set_time_now(const time_spec_t &time_spec, size_t mboard){ + if (mboard != ALL_MBOARDS){ + _tree->access<time_spec_t>(mb_root(mboard) / "time/now").set(time_spec); + return; + } + for (size_t m = 0; m < get_num_mboards(); m++){ + set_time_now(time_spec, m); + } + } + + void set_time_next_pps(const time_spec_t &time_spec, size_t mboard){ + if (mboard != ALL_MBOARDS){ + _tree->access<time_spec_t>(mb_root(mboard) / "time/pps").set(time_spec); + return; + } + for (size_t m = 0; m < get_num_mboards(); m++){ + set_time_next_pps(time_spec, m); + } + } + + void set_time_unknown_pps(const time_spec_t &time_spec){ + UHD_MSG(status) << " 1) catch time transition at pps edge" << std::endl; + time_spec_t time_start = get_time_now(); + time_spec_t time_start_last_pps = get_time_last_pps(); + while(true){ + if (get_time_last_pps() != time_start_last_pps) break; + if ((get_time_now() - time_start) > time_spec_t(1.1)){ + throw uhd::runtime_error( + "Board 0 may not be getting a PPS signal!\n" + "No PPS detected within the time interval.\n" + "See the application notes for your device.\n" + ); + } + } + + UHD_MSG(status) << " 2) set times next pps (synchronously)" << std::endl; + set_time_next_pps(time_spec, ALL_MBOARDS); + boost::this_thread::sleep(boost::posix_time::seconds(1)); + + //verify that the time registers are read to be within a few RTT + for (size_t m = 1; m < get_num_mboards(); m++){ + time_spec_t time_0 = this->get_time_now(0); + time_spec_t time_i = this->get_time_now(m); + if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)){ //10 ms: greater than RTT but not too big + UHD_MSG(warning) << boost::format( + "Detected time deviation between board %d and board 0.\n" + "Board 0 time is %f seconds.\n" + "Board %d time is %f seconds.\n" + ) % m % time_0.get_real_secs() % m % time_i.get_real_secs(); + } + } + } + + bool get_time_synchronized(void){ + for (size_t m = 1; m < get_num_mboards(); m++){ + time_spec_t time_0 = this->get_time_now(0); + time_spec_t time_i = this->get_time_now(m); + if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) return false; + } + return true; + } + + void set_command_time(const time_spec_t &, size_t){ + throw uhd::not_implemented_error("Not implemented yet, but we have a very good idea of how to do it."); + } + + void clear_command_time(size_t){ + throw uhd::not_implemented_error("Not implemented yet, but we have a very good idea of how to do it."); + } + + void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t chan){ + if (chan != ALL_CHANS){ + _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + return; + } + for (size_t c = 0; c < get_rx_num_channels(); c++){ + issue_stream_cmd(stream_cmd, c); + } + } + + void set_clock_config(const clock_config_t &clock_config, size_t mboard){ + //set the reference source... + std::string clock_source; + switch(clock_config.ref_source){ + case clock_config_t::REF_INT: clock_source = "internal"; break; + case clock_config_t::PPS_SMA: clock_source = "external"; break; + case clock_config_t::PPS_MIMO: clock_source = "mimo"; break; + default: clock_source = "unknown"; + } + this->set_clock_source(clock_source, mboard); + + //set the time source + std::string time_source; + switch(clock_config.pps_source){ + case clock_config_t::PPS_INT: time_source = "internal"; break; + case clock_config_t::PPS_SMA: time_source = "external"; break; + case clock_config_t::PPS_MIMO: time_source = "mimo"; break; + default: time_source = "unknown"; + } + if (time_source == "external" and clock_config.pps_polarity == clock_config_t::PPS_NEG) time_source = "_external_"; + this->set_time_source(time_source, mboard); + } + + void set_time_source(const std::string &source, const size_t mboard){ + if (mboard != ALL_MBOARDS){ + _tree->access<std::string>(mb_root(mboard) / "time_source" / "value").set(source); + return; + } + for (size_t m = 0; m < get_num_mboards(); m++){ + this->set_time_source(source, m); + } + } + + std::string get_time_source(const size_t mboard){ + return _tree->access<std::string>(mb_root(mboard) / "time_source" / "value").get(); + } + + std::vector<std::string> get_time_sources(const size_t mboard){ + return _tree->access<std::vector<std::string> >(mb_root(mboard) / "time_source" / "options").get(); + } + + void set_clock_source(const std::string &source, const size_t mboard){ + if (mboard != ALL_MBOARDS){ + _tree->access<std::string>(mb_root(mboard) / "clock_source" / "value").set(source); + return; + } + for (size_t m = 0; m < get_num_mboards(); m++){ + this->set_clock_source(source, m); + } + } + + std::string get_clock_source(const size_t mboard){ + return _tree->access<std::string>(mb_root(mboard) / "clock_source" / "value").get(); + } + + std::vector<std::string> get_clock_sources(const size_t mboard){ + return _tree->access<std::vector<std::string> >(mb_root(mboard) / "clock_source" / "options").get(); + } + + size_t get_num_mboards(void){ + return _tree->list("/mboards").size(); + } + + sensor_value_t get_mboard_sensor(const std::string &name, size_t mboard){ + return _tree->access<sensor_value_t>(mb_root(mboard) / "sensors" / name).get(); + } + + std::vector<std::string> get_mboard_sensor_names(size_t mboard){ + return _tree->list(mb_root(mboard) / "sensors"); + } + + void set_user_register(const boost::uint8_t addr, const boost::uint32_t data, size_t mboard){ + if (mboard != ALL_MBOARDS){ + typedef std::pair<boost::uint8_t, boost::uint32_t> user_reg_t; + _tree->access<user_reg_t>(mb_root(mboard) / "user/reg").set(user_reg_t(addr, data)); + return; + } + for (size_t m = 0; m < get_num_mboards(); m++){ + set_user_register(addr, data, m); + } + } + + /******************************************************************* + * RX methods + ******************************************************************/ + void set_rx_subdev_spec(const subdev_spec_t &spec, size_t mboard){ + if (mboard != ALL_MBOARDS){ + _tree->access<subdev_spec_t>(mb_root(mboard) / "rx_subdev_spec").set(spec); + return; + } + for (size_t m = 0; m < get_num_mboards(); m++){ + set_rx_subdev_spec(spec, m); + } + } + + subdev_spec_t get_rx_subdev_spec(size_t mboard){ + return _tree->access<subdev_spec_t>(mb_root(mboard) / "rx_subdev_spec").get(); + } + + size_t get_rx_num_channels(void){ + size_t sum = 0; + for (size_t m = 0; m < get_num_mboards(); m++){ + sum += get_rx_subdev_spec(m).size(); + } + return sum; + } + + std::string get_rx_subdev_name(size_t chan){ + return _tree->access<std::string>(rx_rf_fe_root(chan) / "name").get(); + } + + void set_rx_rate(double rate, size_t chan){ + if (chan != ALL_CHANS){ + _tree->access<double>(rx_dsp_root(chan) / "rate" / "value").set(rate); + do_samp_rate_warning_message(rate, get_rx_rate(chan), "RX"); + return; + } + for (size_t c = 0; c < get_rx_num_channels(); c++){ + set_rx_rate(rate, c); + } + } + + double get_rx_rate(size_t chan){ + return _tree->access<double>(rx_dsp_root(chan) / "rate" / "value").get(); + } + + meta_range_t get_rx_rates(size_t chan){ + return _tree->access<meta_range_t>(rx_dsp_root(chan) / "rate" / "range").get(); + } + + tune_result_t set_rx_freq(const tune_request_t &tune_request, size_t chan){ + tune_result_t r = tune_xx_subdev_and_dsp(RX_SIGN, _tree->subtree(rx_dsp_root(chan)), _tree->subtree(rx_rf_fe_root(chan)), tune_request); + do_tune_freq_warning_message(tune_request, get_rx_freq(chan), "RX"); + return r; + } + + double get_rx_freq(size_t chan){ + return derive_freq_from_xx_subdev_and_dsp(RX_SIGN, _tree->subtree(rx_dsp_root(chan)), _tree->subtree(rx_rf_fe_root(chan))); + } + + freq_range_t get_rx_freq_range(size_t chan){ + return make_overall_tune_range( + _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "freq" / "range").get(), + _tree->access<meta_range_t>(rx_dsp_root(chan) / "freq" / "range").get(), + this->get_rx_bandwidth(chan) + ); + } + + void set_rx_gain(double gain, const std::string &name, size_t chan){ + return rx_gain_group(chan)->set_value(gain, name); + } + + double get_rx_gain(const std::string &name, size_t chan){ + return rx_gain_group(chan)->get_value(name); + } + + gain_range_t get_rx_gain_range(const std::string &name, size_t chan){ + return rx_gain_group(chan)->get_range(name); + } + + std::vector<std::string> get_rx_gain_names(size_t chan){ + return rx_gain_group(chan)->get_names(); + } + + void set_rx_antenna(const std::string &ant, size_t chan){ + _tree->access<std::string>(rx_rf_fe_root(chan) / "antenna" / "value").set(ant); + } + + std::string get_rx_antenna(size_t chan){ + return _tree->access<std::string>(rx_rf_fe_root(chan) / "antenna" / "value").get(); + } + + std::vector<std::string> get_rx_antennas(size_t chan){ + return _tree->access<std::vector<std::string> >(rx_rf_fe_root(chan) / "antenna" / "options").get(); + } + + void set_rx_bandwidth(double bandwidth, size_t chan){ + _tree->access<double>(rx_rf_fe_root(chan) / "bandwidth" / "value").set(bandwidth); + } + + double get_rx_bandwidth(size_t chan){ + return _tree->access<double>(rx_rf_fe_root(chan) / "bandwidth" / "value").get(); + } + + meta_range_t get_rx_bandwidth_range(size_t chan){ + return _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "bandwidth" / "range").get(); + } + + dboard_iface::sptr get_rx_dboard_iface(size_t chan){ + return _tree->access<dboard_iface::sptr>(rx_rf_fe_root(chan).branch_path().branch_path() / "iface").get(); + } + + sensor_value_t get_rx_sensor(const std::string &name, size_t chan){ + return _tree->access<sensor_value_t>(rx_rf_fe_root(chan) / "sensors" / name).get(); + } + + std::vector<std::string> get_rx_sensor_names(size_t chan){ + return _tree->list(rx_rf_fe_root(chan) / "sensors"); + } + + void set_rx_dc_offset(const bool enb, size_t chan){ + if (chan != ALL_CHANS){ + _tree->access<bool>(rx_fe_root(chan) / "dc_offset" / "enable").set(enb); + return; + } + for (size_t c = 0; c < get_rx_num_channels(); c++){ + this->set_rx_dc_offset(enb, c); + } + } + + void set_rx_dc_offset(const std::complex<double> &offset, size_t chan){ + if (chan != ALL_CHANS){ + _tree->access<std::complex<double> >(rx_fe_root(chan) / "dc_offset" / "value").set(offset); + return; + } + for (size_t c = 0; c < get_rx_num_channels(); c++){ + this->set_rx_dc_offset(offset, c); + } + } + + void set_rx_iq_balance(const std::complex<double> &offset, size_t chan){ + if (chan != ALL_CHANS){ + _tree->access<std::complex<double> >(rx_fe_root(chan) / "iq_balance" / "value").set(offset); + return; + } + for (size_t c = 0; c < get_rx_num_channels(); c++){ + this->set_rx_iq_balance(offset, c); + } + } + + /******************************************************************* + * TX methods + ******************************************************************/ + void set_tx_subdev_spec(const subdev_spec_t &spec, size_t mboard){ + if (mboard != ALL_MBOARDS){ + _tree->access<subdev_spec_t>(mb_root(mboard) / "tx_subdev_spec").set(spec); + return; + } + for (size_t m = 0; m < get_num_mboards(); m++){ + set_tx_subdev_spec(spec, m); + } + } + + subdev_spec_t get_tx_subdev_spec(size_t mboard){ + return _tree->access<subdev_spec_t>(mb_root(mboard) / "tx_subdev_spec").get(); + } + + std::string get_tx_subdev_name(size_t chan){ + return _tree->access<std::string>(tx_rf_fe_root(chan) / "name").get(); + } + + size_t get_tx_num_channels(void){ + size_t sum = 0; + for (size_t m = 0; m < get_num_mboards(); m++){ + sum += get_tx_subdev_spec(m).size(); + } + return sum; + } + + void set_tx_rate(double rate, size_t chan){ + if (chan != ALL_CHANS){ + _tree->access<double>(tx_dsp_root(chan) / "rate" / "value").set(rate); + do_samp_rate_warning_message(rate, get_tx_rate(chan), "TX"); + return; + } + for (size_t c = 0; c < get_tx_num_channels(); c++){ + set_tx_rate(rate, c); + } + } + + double get_tx_rate(size_t chan){ + return _tree->access<double>(tx_dsp_root(chan) / "rate" / "value").get(); + } + + meta_range_t get_tx_rates(size_t chan){ + return _tree->access<meta_range_t>(tx_dsp_root(chan) / "rate" / "range").get(); + } + + tune_result_t set_tx_freq(const tune_request_t &tune_request, size_t chan){ + tune_result_t r = tune_xx_subdev_and_dsp(TX_SIGN, _tree->subtree(tx_dsp_root(chan)), _tree->subtree(tx_rf_fe_root(chan)), tune_request); + do_tune_freq_warning_message(tune_request, get_tx_freq(chan), "TX"); + return r; + } + + double get_tx_freq(size_t chan){ + return derive_freq_from_xx_subdev_and_dsp(TX_SIGN, _tree->subtree(tx_dsp_root(chan)), _tree->subtree(tx_rf_fe_root(chan))); + } + + freq_range_t get_tx_freq_range(size_t chan){ + return make_overall_tune_range( + _tree->access<meta_range_t>(tx_rf_fe_root(chan) / "freq" / "range").get(), + _tree->access<meta_range_t>(tx_dsp_root(chan) / "freq" / "range").get(), + this->get_tx_bandwidth(chan) + ); + } + + void set_tx_gain(double gain, const std::string &name, size_t chan){ + return tx_gain_group(chan)->set_value(gain, name); + } + + double get_tx_gain(const std::string &name, size_t chan){ + return tx_gain_group(chan)->get_value(name); + } + + gain_range_t get_tx_gain_range(const std::string &name, size_t chan){ + return tx_gain_group(chan)->get_range(name); + } + + std::vector<std::string> get_tx_gain_names(size_t chan){ + return tx_gain_group(chan)->get_names(); + } + + void set_tx_antenna(const std::string &ant, size_t chan){ + _tree->access<std::string>(tx_rf_fe_root(chan) / "antenna" / "value").set(ant); + } + + std::string get_tx_antenna(size_t chan){ + return _tree->access<std::string>(tx_rf_fe_root(chan) / "antenna" / "value").get(); + } + + std::vector<std::string> get_tx_antennas(size_t chan){ + return _tree->access<std::vector<std::string> >(tx_rf_fe_root(chan) / "antenna" / "options").get(); + } + + void set_tx_bandwidth(double bandwidth, size_t chan){ + _tree->access<double>(tx_rf_fe_root(chan) / "bandwidth" / "value").set(bandwidth); + } + + double get_tx_bandwidth(size_t chan){ + return _tree->access<double>(tx_rf_fe_root(chan) / "bandwidth" / "value").get(); + } + + meta_range_t get_tx_bandwidth_range(size_t chan){ + return _tree->access<meta_range_t>(tx_rf_fe_root(chan) / "bandwidth" / "range").get(); + } + + dboard_iface::sptr get_tx_dboard_iface(size_t chan){ + return _tree->access<dboard_iface::sptr>(tx_rf_fe_root(chan).branch_path().branch_path() / "iface").get(); + } + + sensor_value_t get_tx_sensor(const std::string &name, size_t chan){ + return _tree->access<sensor_value_t>(tx_rf_fe_root(chan) / "sensors" / name).get(); + } + + std::vector<std::string> get_tx_sensor_names(size_t chan){ + return _tree->list(tx_rf_fe_root(chan) / "sensors"); + } + + void set_tx_dc_offset(const std::complex<double> &offset, size_t chan){ + if (chan != ALL_CHANS){ + _tree->access<std::complex<double> >(tx_fe_root(chan) / "dc_offset" / "value").set(offset); + return; + } + for (size_t c = 0; c < get_tx_num_channels(); c++){ + this->set_tx_dc_offset(offset, c); + } + } + + void set_tx_iq_balance(const std::complex<double> &offset, size_t chan){ + if (chan != ALL_CHANS){ + _tree->access<std::complex<double> >(tx_fe_root(chan) / "iq_balance" / "value").set(offset); + return; + } + for (size_t c = 0; c < get_tx_num_channels(); c++){ + this->set_tx_iq_balance(offset, c); + } + } + +private: + device::sptr _dev; + property_tree::sptr _tree; + + struct mboard_chan_pair{ + size_t mboard, chan; + mboard_chan_pair(void): mboard(0), chan(0){} + }; + + mboard_chan_pair rx_chan_to_mcp(size_t chan){ + mboard_chan_pair mcp; + mcp.chan = chan; + for (mcp.mboard = 0; mcp.mboard < get_num_mboards(); mcp.mboard++){ + size_t sss = get_rx_subdev_spec(mcp.mboard).size(); + if (mcp.chan < sss) break; + mcp.chan -= sss; + } + return mcp; + } + + mboard_chan_pair tx_chan_to_mcp(size_t chan){ + mboard_chan_pair mcp; + mcp.chan = chan; + for (mcp.mboard = 0; mcp.mboard < get_num_mboards(); mcp.mboard++){ + size_t sss = get_tx_subdev_spec(mcp.mboard).size(); + if (mcp.chan < sss) break; + mcp.chan -= sss; + } + return mcp; + } + + fs_path mb_root(const size_t mboard){ + const std::string name = _tree->list("/mboards").at(mboard); + return "/mboards/" + name; + } + + fs_path rx_dsp_root(const size_t chan){ + mboard_chan_pair mcp = rx_chan_to_mcp(chan); + const std::string name = _tree->list(mb_root(mcp.mboard) / "rx_dsps").at(mcp.chan); + return mb_root(mcp.mboard) / "rx_dsps" / name; + } + + fs_path tx_dsp_root(const size_t chan){ + mboard_chan_pair mcp = tx_chan_to_mcp(chan); + const std::string name = _tree->list(mb_root(mcp.mboard) / "tx_dsps").at(mcp.chan); + return mb_root(mcp.mboard) / "tx_dsps" / name; + } + + fs_path rx_fe_root(const size_t chan){ + mboard_chan_pair mcp = rx_chan_to_mcp(chan); + const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); + return mb_root(mcp.mboard) / "rx_frontends" / spec.db_name; + } + + fs_path tx_fe_root(const size_t chan){ + mboard_chan_pair mcp = tx_chan_to_mcp(chan); + const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); + return mb_root(mcp.mboard) / "tx_frontends" / spec.db_name; + } + + fs_path rx_rf_fe_root(const size_t chan){ + mboard_chan_pair mcp = rx_chan_to_mcp(chan); + const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); + return mb_root(mcp.mboard) / "dboards" / spec.db_name / "rx_frontends" / spec.sd_name; + } + + fs_path tx_rf_fe_root(const size_t chan){ + mboard_chan_pair mcp = tx_chan_to_mcp(chan); + const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); + return mb_root(mcp.mboard) / "dboards" / spec.db_name / "tx_frontends" / spec.sd_name; + } + + gain_group::sptr rx_gain_group(size_t chan){ + mboard_chan_pair mcp = rx_chan_to_mcp(chan); + const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); + gain_group::sptr gg = gain_group::make(); + BOOST_FOREACH(const std::string &name, _tree->list(mb_root(mcp.mboard) / "rx_codecs" / spec.db_name / "gains")){ + gg->register_fcns("ADC-"+name, make_gain_fcns_from_subtree(_tree->subtree(mb_root(mcp.mboard) / "rx_codecs" / spec.db_name / "gains" / name)), 0 /* low prio */); + } + BOOST_FOREACH(const std::string &name, _tree->list(rx_rf_fe_root(chan) / "gains")){ + gg->register_fcns(name, make_gain_fcns_from_subtree(_tree->subtree(rx_rf_fe_root(chan) / "gains" / name)), 1 /* high prio */); + } + return gg; + } + + gain_group::sptr tx_gain_group(size_t chan){ + mboard_chan_pair mcp = tx_chan_to_mcp(chan); + const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); + gain_group::sptr gg = gain_group::make(); + BOOST_FOREACH(const std::string &name, _tree->list(mb_root(mcp.mboard) / "tx_codecs" / spec.db_name / "gains")){ + gg->register_fcns("ADC-"+name, make_gain_fcns_from_subtree(_tree->subtree(mb_root(mcp.mboard) / "tx_codecs" / spec.db_name / "gains" / name)), 1 /* high prio */); + } + BOOST_FOREACH(const std::string &name, _tree->list(tx_rf_fe_root(chan) / "gains")){ + gg->register_fcns(name, make_gain_fcns_from_subtree(_tree->subtree(tx_rf_fe_root(chan) / "gains" / name)), 0 /* low prio */); + } + return gg; + } +}; + +/*********************************************************************** + * The Make Function + **********************************************************************/ +multi_usrp::sptr multi_usrp::make(const device_addr_t &dev_addr){ + return sptr(new multi_usrp_impl(dev_addr)); +} diff --git a/host/lib/usrp/subdev_spec.cpp b/host/lib/usrp/subdev_spec.cpp new file mode 100644 index 000000000..6912afec8 --- /dev/null +++ b/host/lib/usrp/subdev_spec.cpp @@ -0,0 +1,80 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/exception.hpp> +#include <boost/algorithm/string.hpp> //for split +#include <boost/tokenizer.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <sstream> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; + +#define pair_tokenizer(inp) \ + boost::tokenizer<boost::char_separator<char> > \ + (inp, boost::char_separator<char>(" ")) + +subdev_spec_pair_t::subdev_spec_pair_t( + const std::string &db_name, const std::string &sd_name +): + db_name(db_name), + sd_name(sd_name) +{ + /* NOP */ +} + +bool usrp::operator==(const subdev_spec_pair_t &lhs, const subdev_spec_pair_t &rhs){ + return (lhs.db_name == rhs.db_name) and (lhs.sd_name == rhs.sd_name); +} + +subdev_spec_t::subdev_spec_t(const std::string &markup){ + BOOST_FOREACH(const std::string &pair, pair_tokenizer(markup)){ + if (pair.empty()) continue; + std::vector<std::string> db_sd; boost::split(db_sd, pair, boost::is_any_of(":")); + switch(db_sd.size()){ + case 1: this->push_back(subdev_spec_pair_t("", db_sd.front())); break; + case 2: this->push_back(subdev_spec_pair_t(db_sd.front(), db_sd.back())); break; + default: throw uhd::value_error("invalid subdev-spec markup string: "+markup); + } + } +} + +std::string subdev_spec_t::to_pp_string(void) const{ + if (this->size() == 0) return "Empty Subdevice Specification"; + + std::stringstream ss; + size_t count = 0; + ss << "Subdevice Specification:" << std::endl; + BOOST_FOREACH(const subdev_spec_pair_t &pair, *this){ + ss << boost::format( + " Channel %d: Daughterboard %s, Subdevice %s" + ) % (count++) % pair.db_name % pair.sd_name << std::endl; + } + return ss.str(); +} + +std::string subdev_spec_t::to_string(void) const{ + std::string markup; + size_t count = 0; + BOOST_FOREACH(const subdev_spec_pair_t &pair, *this){ + markup += ((count++)? " " : "") + pair.db_name + ":" + pair.sd_name; + } + return markup; +} diff --git a/host/lib/usrp/usrp1/CMakeLists.txt b/host/lib/usrp/usrp1/CMakeLists.txt new file mode 100644 index 000000000..70bebe502 --- /dev/null +++ b/host/lib/usrp/usrp1/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP1 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF) + +IF(ENABLE_USRP1) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/soft_time_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrp1_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrp1_impl.cpp + ) +ENDIF(ENABLE_USRP1) diff --git a/host/lib/usrp/usrp1/codec_ctrl.cpp b/host/lib/usrp/usrp1/codec_ctrl.cpp new file mode 100644 index 000000000..c82569ea3 --- /dev/null +++ b/host/lib/usrp/usrp1/codec_ctrl.cpp @@ -0,0 +1,419 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/assign/list_of.hpp> +#include <iomanip> + +using namespace uhd; + +const gain_range_t usrp1_codec_ctrl::tx_pga_gain_range(-20, 0, double(0.1)); +const gain_range_t usrp1_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class usrp1_codec_ctrl_impl : public usrp1_codec_ctrl { +public: + //structors + usrp1_codec_ctrl_impl(spi_iface::sptr iface, int spi_slave); + ~usrp1_codec_ctrl_impl(void); + + //aux adc and dac control + double read_aux_adc(aux_adc_t which); + void write_aux_dac(aux_dac_t which, double volts); + + //duc control + void set_duc_freq(double freq, double); + void enable_tx_digital(bool enb); + + //pga gain control + void set_tx_pga_gain(double); + double get_tx_pga_gain(void); + void set_rx_pga_gain(double, char); + double get_rx_pga_gain(char); + + //rx adc buffer control + void bypass_adc_buffers(bool bypass); + +private: + spi_iface::sptr _iface; + int _spi_slave; + ad9862_regs_t _ad9862_regs; + void send_reg(boost::uint8_t addr); + void recv_reg(boost::uint8_t addr); + + double coarse_tune(double codec_rate, double freq); + double fine_tune(double codec_rate, double freq); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +usrp1_codec_ctrl_impl::usrp1_codec_ctrl_impl(spi_iface::sptr iface, int spi_slave){ + _iface = iface; + _spi_slave = spi_slave; + + //soft reset + _ad9862_regs.soft_reset = 1; + this->send_reg(0); + + //initialize the codec register settings + _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; + _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; + _ad9862_regs.soft_reset = 0; + + //setup rx side of codec + _ad9862_regs.byp_buffer_a = 1; + _ad9862_regs.byp_buffer_b = 1; + _ad9862_regs.buffer_a_pd = 1; + _ad9862_regs.buffer_b_pd = 1; + _ad9862_regs.rx_pga_a = 0; + _ad9862_regs.rx_pga_b = 0; + _ad9862_regs.rx_twos_comp = 1; + _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; + + //setup tx side of codec + _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; + _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; + _ad9862_regs.tx_pga_gain = 199; + _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; + _ad9862_regs.interp = ad9862_regs_t::INTERP_4; + _ad9862_regs.tx_twos_comp = 1; + _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_NCO; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; + _ad9862_regs.dac_a_coarse_gain = 0x3; + _ad9862_regs.dac_b_coarse_gain = 0x3; + + //setup the dll + _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; + _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; + _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; + + //setup clockout + _ad9862_regs.clkout2_div_factor = ad9862_regs_t::CLKOUT2_DIV_FACTOR_2; + + //write the register settings to the codec + for (boost::uint8_t addr = 0; addr <= 25; addr++) { + this->send_reg(addr); + } + + //always start conversions for aux ADC + _ad9862_regs.start_a = 1; + _ad9862_regs.start_b = 1; + + //aux adc clock + _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; + this->send_reg(34); +} + +usrp1_codec_ctrl_impl::~usrp1_codec_ctrl_impl(void){UHD_SAFE_CALL( + //set aux dacs to zero + this->write_aux_dac(AUX_DAC_A, 0); + this->write_aux_dac(AUX_DAC_B, 0); + this->write_aux_dac(AUX_DAC_C, 0); + this->write_aux_dac(AUX_DAC_D, 0); + + //power down + _ad9862_regs.all_rx_pd = 1; + this->send_reg(1); + _ad9862_regs.tx_digital_pd = 1; + _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; + this->send_reg(8); +)} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void usrp1_codec_ctrl_impl::set_tx_pga_gain(double gain){ + int gain_word = int(mtpgw*(gain - tx_pga_gain_range.start())/(tx_pga_gain_range.stop() - tx_pga_gain_range.start())); + _ad9862_regs.tx_pga_gain = uhd::clip(gain_word, 0, mtpgw); + this->send_reg(16); +} + +double usrp1_codec_ctrl_impl::get_tx_pga_gain(void){ + return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.stop() - tx_pga_gain_range.start())/mtpgw) + tx_pga_gain_range.start(); +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void usrp1_codec_ctrl_impl::set_rx_pga_gain(double gain, char which){ + int gain_word = int(mrpgw*(gain - rx_pga_gain_range.start())/(rx_pga_gain_range.stop() - rx_pga_gain_range.start())); + gain_word = uhd::clip(gain_word, 0, mrpgw); + switch(which){ + case 'A': + _ad9862_regs.rx_pga_a = gain_word; + this->send_reg(2); + return; + case 'B': + _ad9862_regs.rx_pga_b = gain_word; + this->send_reg(3); + return; + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +double usrp1_codec_ctrl_impl::get_rx_pga_gain(char which){ + int gain_word; + switch(which){ + case 'A': gain_word = _ad9862_regs.rx_pga_a; break; + case 'B': gain_word = _ad9862_regs.rx_pga_b; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + return (gain_word*(rx_pga_gain_range.stop() - rx_pga_gain_range.start())/mrpgw) + rx_pga_gain_range.start(); +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static double aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low) +{ + return double(((boost::uint16_t(high) << 2) | low)*3.3)/0x3ff; +} + +double usrp1_codec_ctrl_impl::read_aux_adc(aux_adc_t which){ + switch(which){ + case AUX_ADC_A1: + _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC1; + this->send_reg(34); //start conversion and select mux + this->recv_reg(28); //read the value (2 bytes, 2 reads) + this->recv_reg(29); + return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); + + case AUX_ADC_A2: + _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC2; + this->send_reg(34); //start conversion and select mux + this->recv_reg(26); //read the value (2 bytes, 2 reads) + this->recv_reg(27); + return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); + + case AUX_ADC_B1: + _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC1; + this->send_reg(34); //start conversion and select mux + this->recv_reg(32); //read the value (2 bytes, 2 reads) + this->recv_reg(33); + return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); + + case AUX_ADC_B2: + _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC2; + this->send_reg(34); //start conversion and select mux + this->recv_reg(30); //read the value (2 bytes, 2 reads) + this->recv_reg(31); + return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void usrp1_codec_ctrl_impl::write_aux_dac(aux_dac_t which, double volts) +{ + //special case for aux dac d (aka sigma delta word) + if (which == AUX_DAC_D) { + boost::uint16_t dac_word = uhd::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); + _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); + _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); + this->send_reg(42); + this->send_reg(43); + return; + } + + //calculate the dac word for aux dac a, b, c + boost::uint8_t dac_word = uhd::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + + //setup a lookup table for the aux dac params (reg ref, reg addr) + typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; + uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of + (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) + (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) + (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) + ; + + //set the aux dac register + UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); + boost::uint8_t *reg_ref, reg_addr; + boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; + *reg_ref = dac_word; + this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void usrp1_codec_ctrl_impl::send_reg(boost::uint8_t addr) +{ + boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); + + UHD_LOGV(often) + << "codec control write reg: 0x" + << std::setw(8) << std::hex << reg << std::endl + ; + _iface->write_spi(_spi_slave, + spi_config_t::EDGE_RISE, reg, 16); +} + +void usrp1_codec_ctrl_impl::recv_reg(boost::uint8_t addr) +{ + boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); + + UHD_LOGV(often) + << "codec control read reg: 0x" + << std::setw(8) << std::hex << reg << std::endl + ; + + boost::uint32_t ret = _iface->read_spi(_spi_slave, + spi_config_t::EDGE_RISE, reg, 16); + + UHD_LOGV(often) + << "codec control read ret: 0x" + << std::setw(8) << std::hex << ret << std::endl + ; + + _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * DUC tuning + **********************************************************************/ +double usrp1_codec_ctrl_impl::coarse_tune(double codec_rate, double freq) +{ + double coarse_freq; + + double coarse_freq_1 = codec_rate / 8; + double coarse_freq_2 = codec_rate / 4; + double coarse_limit_1 = coarse_freq_1 / 2; + double coarse_limit_2 = (coarse_freq_1 + coarse_freq_2) / 2; + double max_freq = coarse_freq_2 + .09375 * codec_rate; + + if (freq < -max_freq) { + return false; + } + else if (freq < -coarse_limit_2) { + _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_NEG_SHIFT; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_4; + coarse_freq = -coarse_freq_2; + } + else if (freq < -coarse_limit_1) { + _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_NEG_SHIFT; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_8; + coarse_freq = -coarse_freq_1; + } + else if (freq < coarse_limit_1) { + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; + coarse_freq = 0; + } + else if (freq < coarse_limit_2) { + _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_POS_SHIFT; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_8; + coarse_freq = coarse_freq_1; + } + else if (freq <= max_freq) { + _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_POS_SHIFT; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_4; + coarse_freq = coarse_freq_2; + } + else { + return 0; + } + + return coarse_freq; +} + +double usrp1_codec_ctrl_impl::fine_tune(double codec_rate, double target_freq) +{ + static const double scale_factor = std::pow(2.0, 24); + + boost::uint32_t freq_word = boost::uint32_t( + boost::math::round(abs((target_freq / codec_rate) * scale_factor))); + + double actual_freq = freq_word * codec_rate / scale_factor; + + if (target_freq < 0) { + _ad9862_regs.neg_fine_tune = ad9862_regs_t::NEG_FINE_TUNE_NEG_SHIFT; + actual_freq = -actual_freq; + } + else { + _ad9862_regs.neg_fine_tune = ad9862_regs_t::NEG_FINE_TUNE_POS_SHIFT; + } + + _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_NCO; + _ad9862_regs.ftw_23_16 = (freq_word >> 16) & 0xff; + _ad9862_regs.ftw_15_8 = (freq_word >> 8) & 0xff; + _ad9862_regs.ftw_7_0 = (freq_word >> 0) & 0xff; + + return actual_freq; +} + +void usrp1_codec_ctrl_impl::set_duc_freq(double freq, double rate) +{ + double codec_rate = rate * 2; + double coarse_freq = coarse_tune(codec_rate, freq); + double fine_freq = fine_tune(codec_rate / 4, freq - coarse_freq); + + UHD_LOG + << "ad9862 tuning result:" << std::endl + << " requested: " << freq << std::endl + << " actual: " << coarse_freq + fine_freq << std::endl + << " coarse freq: " << coarse_freq << std::endl + << " fine freq: " << fine_freq << std::endl + << " codec rate: " << codec_rate << std::endl + ; + + this->send_reg(20); + this->send_reg(21); + this->send_reg(22); + this->send_reg(23); +} + +void usrp1_codec_ctrl_impl::enable_tx_digital(bool enb){ + _ad9862_regs.tx_digital_pd = (enb)? 0 : 1; + this->send_reg(8); +} + +/*********************************************************************** + * Codec Control ADC buffer bypass + * Disable this for AC-coupled daughterboards (TVRX) + * By default it is initialized TRUE. + **********************************************************************/ +void usrp1_codec_ctrl_impl::bypass_adc_buffers(bool bypass) { + _ad9862_regs.byp_buffer_a = bypass; + _ad9862_regs.byp_buffer_b = bypass; + this->send_reg(2); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +usrp1_codec_ctrl::sptr usrp1_codec_ctrl::make(spi_iface::sptr iface, + int spi_slave) +{ + return sptr(new usrp1_codec_ctrl_impl(iface, spi_slave)); +} diff --git a/host/lib/usrp/usrp1/codec_ctrl.hpp b/host/lib/usrp/usrp1/codec_ctrl.hpp new file mode 100644 index 000000000..70f4e0b61 --- /dev/null +++ b/host/lib/usrp/usrp1/codec_ctrl.hpp @@ -0,0 +1,99 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP1_CODEC_CTRL_HPP +#define INCLUDED_USRP1_CODEC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp1 codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class usrp1_codec_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp1_codec_ctrl> sptr; + + static const uhd::gain_range_t tx_pga_gain_range; + static const uhd::gain_range_t rx_pga_gain_range; + + /*! + * Make a new clock control object. + * \param iface the spi iface object + * \param spi_slave which spi device + */ + static sptr make(uhd::spi_iface::sptr iface, int spi_slave); + + //! aux adc identifier constants + enum aux_adc_t{ + AUX_ADC_A2 = 0xA2, + AUX_ADC_A1 = 0xA1, + AUX_ADC_B2 = 0xB2, + AUX_ADC_B1 = 0xB1 + }; + + /*! + * Read an auxiliary adc: + * The internals remember which aux adc was read last. + * Therefore, the aux adc switch is only changed as needed. + * \param which which of the 4 adcs + * \return a value in volts + */ + virtual double read_aux_adc(aux_adc_t which) = 0; + + //! aux dac identifier constants + enum aux_dac_t{ + AUX_DAC_A = 0xA, + AUX_DAC_B = 0xB, + AUX_DAC_C = 0xC, + AUX_DAC_D = 0xD + }; + + /*! + * Write an auxiliary dac. + * \param which which of the 4 dacs + * \param volts the level in in volts + */ + virtual void write_aux_dac(aux_dac_t which, double volts) = 0; + + //! Set the TX PGA gain + virtual void set_tx_pga_gain(double gain) = 0; + + //! Get the TX PGA gain + virtual double get_tx_pga_gain(void) = 0; + + //! Set the RX PGA gain ('A' or 'B') + virtual void set_rx_pga_gain(double gain, char which) = 0; + + //! Get the RX PGA gain ('A' or 'B') + virtual double get_rx_pga_gain(char which) = 0; + + //! Set the TX modulator frequency + virtual void set_duc_freq(double freq, double rate) = 0; + + //! Enable or disable the digital part of the DAC + virtual void enable_tx_digital(bool enb) = 0; + + //! Enable or disable ADC buffer bypass + virtual void bypass_adc_buffers(bool bypass) = 0; +}; + +#endif /* INCLUDED_USRP1_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp new file mode 100644 index 000000000..34bbe1893 --- /dev/null +++ b/host/lib/usrp/usrp1/dboard_iface.cpp @@ -0,0 +1,397 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_iface.hpp" +#include "usrp1_impl.hpp" +#include "fpga_regs_common.h" +#include "usrp_spi_defs.h" +#include "fpga_regs_standard.h" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +static const dboard_id_t tvrx_id(0x0040); + +class usrp1_dboard_iface : public dboard_iface { +public: + + usrp1_dboard_iface(usrp1_iface::sptr iface, + usrp1_codec_ctrl::sptr codec, + usrp1_impl::dboard_slot_t dboard_slot, + const double &master_clock_rate, + const dboard_id_t &rx_dboard_id + ): + _dboard_slot(dboard_slot), + _master_clock_rate(master_clock_rate), + _rx_dboard_id(rx_dboard_id) + { + _iface = iface; + _codec = codec; + + _dbsrx_classic_div = 1; + + //yes this is evil but it's necessary for TVRX to work on USRP1 + if(_rx_dboard_id == tvrx_id) _codec->bypass_adc_buffers(false); + //else _codec->bypass_adc_buffers(false); //don't think this is necessary + } + + ~usrp1_dboard_iface() + { + /* NOP */ + } + + special_props_t get_special_props() + { + special_props_t props; + props.soft_clock_divider = true; + props.mangle_i2c_addrs = (_dboard_slot == usrp1_impl::DBOARD_SLOT_B); + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, double); + double read_aux_adc(unit_t, aux_adc_t); + + void _set_pin_ctrl(unit_t, boost::uint16_t); + void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); + void _set_gpio_ddr(unit_t, boost::uint16_t); + void _set_gpio_out(unit_t, boost::uint16_t); + void set_gpio_debug(unit_t, int); + boost::uint16_t read_gpio(unit_t); + + void write_i2c(boost::uint8_t, const byte_vector_t &); + byte_vector_t read_i2c(boost::uint8_t, size_t); + + void write_spi(unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits); + + boost::uint32_t read_write_spi(unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits); + + void set_clock_rate(unit_t, double); + std::vector<double> get_clock_rates(unit_t); + double get_clock_rate(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + +private: + usrp1_iface::sptr _iface; + usrp1_codec_ctrl::sptr _codec; + unsigned _dbsrx_classic_div; + const usrp1_impl::dboard_slot_t _dboard_slot; + const double &_master_clock_rate; + const dboard_id_t _rx_dboard_id; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr usrp1_impl::make_dboard_iface(usrp1_iface::sptr iface, + usrp1_codec_ctrl::sptr codec, + usrp1_impl::dboard_slot_t dboard_slot, + const double &master_clock_rate, + const dboard_id_t &rx_dboard_id +){ + return dboard_iface::sptr(new usrp1_dboard_iface( + iface, codec, dboard_slot, master_clock_rate, rx_dboard_id + )); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +static const dboard_id_t dbsrx_classic_id(0x0002); + +/* + * Daughterboard reference clock register + * + * Bit 7 - 1 turns on refclk, 0 allows IO use + * Bits 6:0 - Divider value + */ +void usrp1_dboard_iface::set_clock_rate(unit_t unit, double rate) +{ + assert_has(this->get_clock_rates(unit), rate, "dboard clock rate"); + + if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ + _dbsrx_classic_div = size_t(_master_clock_rate/rate); + switch(_dboard_slot){ + case usrp1_impl::DBOARD_SLOT_A: + _iface->poke32(FR_RX_A_REFCLK, (_dbsrx_classic_div & 0x7f) | 0x80); + break; + + case usrp1_impl::DBOARD_SLOT_B: + _iface->poke32(FR_RX_B_REFCLK, (_dbsrx_classic_div & 0x7f) | 0x80); + break; + } + } +} + +std::vector<double> usrp1_dboard_iface::get_clock_rates(unit_t unit) +{ + std::vector<double> rates; + if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ + for (size_t div = 1; div <= 127; div++) + rates.push_back(_master_clock_rate / div); + } + else{ + rates.push_back(_master_clock_rate); + } + return rates; +} + +double usrp1_dboard_iface::get_clock_rate(unit_t unit) +{ + if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ + return _master_clock_rate/_dbsrx_classic_div; + } + return _master_clock_rate; +} + +void usrp1_dboard_iface::set_clock_enabled(unit_t, bool) +{ + //TODO we can only enable for special case anyway... +} + +double usrp1_dboard_iface::get_codec_rate(unit_t){ + return _master_clock_rate; +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) +{ + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_MASK_1, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_MASK_3, value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_MASK_0, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_MASK_2, value); + break; + } +} + +void usrp1_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) +{ + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_OE_1, 0xffff0000 | value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_OE_3, 0xffff0000 | value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_OE_0, 0xffff0000 | value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_OE_2, 0xffff0000 | value); + break; + } +} + +void usrp1_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) +{ + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_IO_1, 0xffff0000 | value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_IO_3, 0xffff0000 | value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_IO_0, 0xffff0000 | value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_IO_2, 0xffff0000 | value); + break; + } +} + +void usrp1_dboard_iface::set_gpio_debug(unit_t, int) +{ + /* NOP */ +} + +boost::uint16_t usrp1_dboard_iface::read_gpio(unit_t unit) +{ + boost::uint32_t out_value; + + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + out_value = _iface->peek32(1); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + out_value = _iface->peek32(2); + else + UHD_THROW_INVALID_CODE_PATH(); + + switch(unit) { + case UNIT_RX: + return (boost::uint16_t)((out_value >> 16) & 0x0000ffff); + case UNIT_TX: + return (boost::uint16_t)((out_value >> 0) & 0x0000ffff); + } + UHD_ASSERT_THROW(false); +} + +void usrp1_dboard_iface::_set_atr_reg(unit_t unit, + atr_reg_t atr, boost::uint16_t value) +{ + // Ignore unsupported states + if ((atr == ATR_REG_IDLE) || (atr == ATR_REG_TX_ONLY)) + return; + if(atr == ATR_REG_RX_ONLY) { + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_RXVAL_1, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_RXVAL_3, value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_RXVAL_0, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_RXVAL_2, value); + break; + } + } else if (atr == ATR_REG_FULL_DUPLEX) { + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_TXVAL_1, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_TXVAL_3, value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_TXVAL_0, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_TXVAL_2, value); + break; + } + } +} +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \param slot the side (A or B) the dboard is attached + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit, + usrp1_impl::dboard_slot_t slot) +{ + switch(unit) { + case dboard_iface::UNIT_TX: + if (slot == usrp1_impl::DBOARD_SLOT_A) + return SPI_ENABLE_TX_A; + else if (slot == usrp1_impl::DBOARD_SLOT_B) + return SPI_ENABLE_TX_B; + else + break; + case dboard_iface::UNIT_RX: + if (slot == usrp1_impl::DBOARD_SLOT_A) + return SPI_ENABLE_RX_A; + else if (slot == usrp1_impl::DBOARD_SLOT_B) + return SPI_ENABLE_RX_B; + else + break; + } + UHD_THROW_INVALID_CODE_PATH(); +} + +void usrp1_dboard_iface::write_spi(unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits) +{ + _iface->write_spi(unit_to_otw_spi_dev(unit, _dboard_slot), + config, data, num_bits); +} + +boost::uint32_t usrp1_dboard_iface::read_write_spi(unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits) +{ + return _iface->read_spi(unit_to_otw_spi_dev(unit, _dboard_slot), + config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp1_dboard_iface::write_i2c(boost::uint8_t addr, + const byte_vector_t &bytes) +{ + return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp1_dboard_iface::read_i2c(boost::uint8_t addr, + size_t num_bytes) +{ + return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp1_dboard_iface::write_aux_dac(dboard_iface::unit_t, + aux_dac_t which, double value) +{ + //same aux dacs for each unit + static const uhd::dict<aux_dac_t, usrp1_codec_ctrl::aux_dac_t> + which_to_aux_dac = map_list_of + (AUX_DAC_A, usrp1_codec_ctrl::AUX_DAC_A) + (AUX_DAC_B, usrp1_codec_ctrl::AUX_DAC_B) + (AUX_DAC_C, usrp1_codec_ctrl::AUX_DAC_C) + (AUX_DAC_D, usrp1_codec_ctrl::AUX_DAC_D); + + _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +double usrp1_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, + aux_adc_t which) +{ + static const + uhd::dict<unit_t, uhd::dict<aux_adc_t, usrp1_codec_ctrl::aux_adc_t> > + unit_to_which_to_aux_adc = map_list_of(UNIT_RX, map_list_of + (AUX_ADC_A, usrp1_codec_ctrl::AUX_ADC_A1) + (AUX_ADC_B, usrp1_codec_ctrl::AUX_ADC_B1)) + (UNIT_TX, map_list_of + (AUX_ADC_A, usrp1_codec_ctrl::AUX_ADC_A2) + (AUX_ADC_B, usrp1_codec_ctrl::AUX_ADC_B2)); + + return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/usrp1/io_impl.cpp b/host/lib/usrp/usrp1/io_impl.cpp new file mode 100644 index 000000000..d256df660 --- /dev/null +++ b/host/lib/usrp/usrp1/io_impl.cpp @@ -0,0 +1,693 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "validate_subdev_spec.hpp" +#define SRPH_DONT_CHECK_SEQUENCE +#include "../../transport/super_recv_packet_handler.hpp" +#define SSPH_DONT_PAD_TO_ONE +#include "../../transport/super_send_packet_handler.hpp" +#include "usrp1_calc_mux.hpp" +#include "fpga_regs_standard.h" +#include "fpga_regs_common.h" +#include "usrp_commands.h" +#include "usrp1_impl.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/math/special_functions/sign.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/thread/thread.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const size_t alignment_padding = 512; + +/*********************************************************************** + * Helper struct to associate an offset with a buffer + **********************************************************************/ +struct offset_send_buffer{ + offset_send_buffer(void){ + /* NOP */ + } + + offset_send_buffer(managed_send_buffer::sptr buff, size_t offset = 0): + buff(buff), offset(offset) + { + /* NOP */ + } + + //member variables + managed_send_buffer::sptr buff; + size_t offset; /* in bytes */ +}; + +/*********************************************************************** + * Reusable managed send buffer to handle aligned commits + **********************************************************************/ +class offset_managed_send_buffer : public managed_send_buffer{ +public: + typedef boost::function<void(offset_send_buffer&, offset_send_buffer&, size_t)> commit_cb_type; + offset_managed_send_buffer(const commit_cb_type &commit_cb): + _commit_cb(commit_cb) + { + /* NOP */ + } + + void commit(size_t size){ + if (size != 0) this->_commit_cb(_curr_buff, _next_buff, size); + } + + sptr get_new( + offset_send_buffer &curr_buff, + offset_send_buffer &next_buff + ){ + _curr_buff = curr_buff; + _next_buff = next_buff; + return make_managed_buffer(this); + } + +private: + void *get_buff(void) const{return _curr_buff.buff->cast<char *>() + _curr_buff.offset;} + size_t get_size(void) const{return _curr_buff.buff->size() - _curr_buff.offset;} + + offset_send_buffer _curr_buff, _next_buff; + commit_cb_type _commit_cb; +}; + +/*********************************************************************** + * BS VRT packer/unpacker functions (since samples don't have headers) + **********************************************************************/ +static void usrp1_bs_vrt_packer( + boost::uint32_t *, + vrt::if_packet_info_t &if_packet_info +){ + if_packet_info.num_header_words32 = 0; + if_packet_info.num_packet_words32 = if_packet_info.num_payload_words32; +} + +static void usrp1_bs_vrt_unpacker( + const boost::uint32_t *, + vrt::if_packet_info_t &if_packet_info +){ + if_packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_DATA; + if_packet_info.num_payload_words32 = if_packet_info.num_packet_words32; + if_packet_info.num_payload_bytes = if_packet_info.num_packet_words32*sizeof(boost::uint32_t); + if_packet_info.num_header_words32 = 0; + if_packet_info.packet_count = 0; + if_packet_info.sob = false; + if_packet_info.eob = false; + if_packet_info.has_sid = false; + if_packet_info.has_cid = false; + if_packet_info.has_tsi = false; + if_packet_info.has_tsf = false; + if_packet_info.has_tlr = false; +} + +/*********************************************************************** + * IO Implementation Details + **********************************************************************/ +struct usrp1_impl::io_impl{ + io_impl(zero_copy_if::sptr data_transport): + data_transport(data_transport), + curr_buff(offset_send_buffer(data_transport->get_send_buff())), + omsb(boost::bind(&usrp1_impl::io_impl::commit_send_buff, this, _1, _2, _3)) + { + /* NOP */ + } + + ~io_impl(void){ + UHD_SAFE_CALL(flush_send_buff();) + } + + zero_copy_if::sptr data_transport; + + //wrapper around the actual send buffer interface + //all of this to ensure only aligned lengths are committed + //NOTE: you must commit before getting a new buffer + //since the vrt packet handler obeys this, we are ok + offset_send_buffer curr_buff; + offset_managed_send_buffer omsb; + void commit_send_buff(offset_send_buffer&, offset_send_buffer&, size_t); + void flush_send_buff(void); + managed_send_buffer::sptr get_send_buff(double timeout){ + //try to get a new managed buffer with timeout + offset_send_buffer next_buff(data_transport->get_send_buff(timeout)); + if (not next_buff.buff.get()) return managed_send_buffer::sptr(); /* propagate timeout here */ + + //make a new managed buffer with the offset buffs + return omsb.get_new(curr_buff, next_buff); + } + + task::sptr vandal_task; + boost::system_time last_send_time; +}; + +/*! + * Perform an actual commit on the send buffer: + * Copy the remainder of alignment to the next buffer. + * Commit the current buffer at multiples of alignment. + */ +void usrp1_impl::io_impl::commit_send_buff( + offset_send_buffer &curr, + offset_send_buffer &next, + size_t num_bytes +){ + //total number of bytes now in the current buffer + size_t bytes_in_curr_buffer = curr.offset + num_bytes; + + //calculate how many to commit and remainder + size_t num_bytes_remaining = bytes_in_curr_buffer % alignment_padding; + size_t num_bytes_to_commit = bytes_in_curr_buffer - num_bytes_remaining; + + //copy the remainder into the next buffer + std::memcpy( + next.buff->cast<char *>() + next.offset, + curr.buff->cast<char *>() + num_bytes_to_commit, + num_bytes_remaining + ); + + //update the offset into the next buffer + next.offset += num_bytes_remaining; + + //commit the current buffer + curr.buff->commit(num_bytes_to_commit); + + //store the next buffer for the next call + curr_buff = next; +} + +/*! + * Flush the current buffer by padding out to alignment and committing. + */ +void usrp1_impl::io_impl::flush_send_buff(void){ + //calculate the number of bytes to alignment + size_t bytes_to_pad = (-1*curr_buff.offset)%alignment_padding; + + //send at least alignment_padding to guarantee zeros are sent + if (bytes_to_pad == 0) bytes_to_pad = alignment_padding; + + //get the buffer, clear, and commit (really current buffer) + managed_send_buffer::sptr buff = this->get_send_buff(.1); + if (buff.get() != NULL){ + std::memset(buff->cast<void *>(), 0, bytes_to_pad); + buff->commit(bytes_to_pad); + } +} + +/*********************************************************************** + * Initialize internals within this file + **********************************************************************/ +void usrp1_impl::io_init(void){ + + _io_impl = UHD_PIMPL_MAKE(io_impl, (_data_transport)); + + //init as disabled, then call the real function (uses restore) + this->enable_rx(false); + this->enable_tx(false); + rx_stream_on_off(false); + tx_stream_on_off(false); + _io_impl->flush_send_buff(); + + //create a new vandal thread to poll xerflow conditions + _io_impl->vandal_task = task::make(boost::bind( + &usrp1_impl::vandal_conquest_loop, this + )); +} + +void usrp1_impl::rx_stream_on_off(bool enb){ + this->restore_rx(enb); + //drain any junk in the receive transport after stop streaming command + while(not enb and _data_transport->get_recv_buff().get() != NULL){ + /* NOP */ + } +} + +void usrp1_impl::tx_stream_on_off(bool enb){ + _io_impl->last_send_time = boost::get_system_time(); + if (_tx_enabled and not enb) _io_impl->flush_send_buff(); + this->restore_tx(enb); +} + +/*! + * Casually poll the overflow and underflow registers. + * On an underflow, push an async message into the queue and print. + * On an overflow, interleave an inline message into recv and print. + * This procedure creates "soft" inline and async user messages. + */ +void usrp1_impl::vandal_conquest_loop(void){ + + //initialize the async metadata + async_metadata_t async_metadata; + async_metadata.channel = 0; + async_metadata.has_time_spec = true; + async_metadata.event_code = async_metadata_t::EVENT_CODE_UNDERFLOW; + + //initialize the inline metadata + rx_metadata_t inline_metadata; + inline_metadata.has_time_spec = true; + inline_metadata.error_code = rx_metadata_t::ERROR_CODE_OVERFLOW; + + //start the polling loop... + try{ while (not boost::this_thread::interruption_requested()){ + boost::uint8_t underflow = 0, overflow = 0; + + //shutoff transmit if it has been too long since send() was called + if (_tx_enabled and (boost::get_system_time() - _io_impl->last_send_time) > boost::posix_time::milliseconds(100)){ + this->tx_stream_on_off(false); + } + + //always poll regardless of enabled so we can clear the conditions + _fx2_ctrl->usrp_control_read( + VRQ_GET_STATUS, 0, GS_TX_UNDERRUN, &underflow, sizeof(underflow) + ); + _fx2_ctrl->usrp_control_read( + VRQ_GET_STATUS, 0, GS_RX_OVERRUN, &overflow, sizeof(overflow) + ); + + //handle message generation for xerflow conditions + if (_tx_enabled and underflow){ + async_metadata.time_spec = _soft_time_ctrl->get_time(); + _soft_time_ctrl->get_async_queue().push_with_pop_on_full(async_metadata); + UHD_MSG(fastpath) << "U"; + } + if (_rx_enabled and overflow){ + inline_metadata.time_spec = _soft_time_ctrl->get_time(); + _soft_time_ctrl->get_inline_queue().push_with_pop_on_full(inline_metadata); + UHD_MSG(fastpath) << "O"; + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + }} + catch(const boost::thread_interrupted &){} //normal exit condition + catch(const std::exception &e){ + UHD_MSG(error) << "The vandal caught an unexpected exception " << e.what() << std::endl; + } +} + +/*********************************************************************** + * RX streamer wrapper that talks to soft time control + **********************************************************************/ +class usrp1_recv_packet_streamer : public sph::recv_packet_handler, public rx_streamer{ +public: + usrp1_recv_packet_streamer(const size_t max_num_samps, soft_time_ctrl::sptr stc){ + _max_num_samps = max_num_samps; + _stc = stc; + } + + size_t get_num_channels(void) const{ + return this->size(); + } + + size_t get_max_num_samps(void) const{ + return _max_num_samps; + } + + size_t recv( + const rx_streamer::buffs_type &buffs, + const size_t nsamps_per_buff, + uhd::rx_metadata_t &metadata, + const double timeout, + const bool one_packet + ){ + //interleave a "soft" inline message into the receive stream: + if (_stc->get_inline_queue().pop_with_haste(metadata)) return 0; + + size_t num_samps_recvd = sph::recv_packet_handler::recv( + buffs, nsamps_per_buff, metadata, timeout, one_packet + ); + + return _stc->recv_post(metadata, num_samps_recvd); + } + +private: + size_t _max_num_samps; + soft_time_ctrl::sptr _stc; +}; + +/*********************************************************************** + * TX streamer wrapper that talks to soft time control + **********************************************************************/ +class usrp1_send_packet_streamer : public sph::send_packet_handler, public tx_streamer{ +public: + usrp1_send_packet_streamer(const size_t max_num_samps, soft_time_ctrl::sptr stc, boost::function<void(bool)> tx_enb_fcn){ + _max_num_samps = max_num_samps; + this->set_max_samples_per_packet(_max_num_samps); + _stc = stc; + _tx_enb_fcn = tx_enb_fcn; + } + + size_t get_num_channels(void) const{ + return this->size(); + } + + size_t get_max_num_samps(void) const{ + return _max_num_samps; + } + + size_t send( + const tx_streamer::buffs_type &buffs, + const size_t nsamps_per_buff, + const uhd::tx_metadata_t &metadata, + const double timeout_ + ){ + double timeout = timeout_; //rw copy + _stc->send_pre(metadata, timeout); + + _tx_enb_fcn(true); //always enable (it will do the right thing) + size_t num_samps_sent = sph::send_packet_handler::send( + buffs, nsamps_per_buff, metadata, timeout + ); + + //handle eob flag (commit the buffer, //disable the DACs) + //check num samps sent to avoid flush on incomplete/timeout + if (metadata.end_of_burst and num_samps_sent == nsamps_per_buff){ + async_metadata_t metadata; + metadata.channel = 0; + metadata.has_time_spec = true; + metadata.time_spec = _stc->get_time(); + metadata.event_code = async_metadata_t::EVENT_CODE_BURST_ACK; + _stc->get_async_queue().push_with_pop_on_full(metadata); + _tx_enb_fcn(false); + } + + return num_samps_sent; + } + +private: + size_t _max_num_samps; + soft_time_ctrl::sptr _stc; + boost::function<void(bool)> _tx_enb_fcn; +}; + +/*********************************************************************** + * Properties callback methods below + **********************************************************************/ +void usrp1_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ + + //sanity checking + validate_subdev_spec(_tree, spec, "rx"); + + _rx_subdev_spec = spec; //shadow + + //set the mux and set the number of rx channels + std::vector<mapping_pair_t> mapping; + BOOST_FOREACH(const subdev_spec_pair_t &pair, spec){ + const std::string conn = _tree->access<std::string>(str(boost::format( + "/mboards/0/dboards/%s/rx_frontends/%s/connection" + ) % pair.db_name % pair.sd_name)).get(); + mapping.push_back(std::make_pair(pair.db_name, conn)); + } + bool s = this->disable_rx(); + _iface->poke32(FR_RX_MUX, calc_rx_mux(mapping)); + this->restore_rx(s); +} + +void usrp1_impl::update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ + + //sanity checking + validate_subdev_spec(_tree, spec, "tx"); + + _tx_subdev_spec = spec; //shadow + + //set the mux and set the number of tx channels + std::vector<mapping_pair_t> mapping; + BOOST_FOREACH(const subdev_spec_pair_t &pair, spec){ + const std::string conn = _tree->access<std::string>(str(boost::format( + "/mboards/0/dboards/%s/tx_frontends/%s/connection" + ) % pair.db_name % pair.sd_name)).get(); + mapping.push_back(std::make_pair(pair.db_name, conn)); + } + bool s = this->disable_tx(); + _iface->poke32(FR_TX_MUX, calc_tx_mux(mapping)); + this->restore_tx(s); +} + +void usrp1_impl::update_tick_rate(const double rate){ + //updating this variable should: + //update dboard iface -> it has a reference + //update dsp freq bounds -> publisher + _master_clock_rate = rate; +} + +uhd::meta_range_t usrp1_impl::get_rx_dsp_host_rates(void){ + meta_range_t range; + const size_t div = this->has_rx_halfband()? 2 : 1; + for (int rate = 256; rate >= 4; rate -= div){ + range.push_back(range_t(_master_clock_rate/rate)); + } + return range; +} + +uhd::meta_range_t usrp1_impl::get_tx_dsp_host_rates(void){ + meta_range_t range; + const size_t div = this->has_tx_halfband()? 2 : 1; + for (int rate = 256; rate >= 8; rate -= div){ + range.push_back(range_t(_master_clock_rate/rate)); + } + return range; +} + +double usrp1_impl::update_rx_samp_rate(size_t dspno, const double samp_rate){ + + const size_t div = this->has_rx_halfband()? 2 : 1; + const size_t rate = boost::math::iround(_master_clock_rate/this->get_rx_dsp_host_rates().clip(samp_rate, true)); + + if (rate < 8 and this->has_rx_halfband()) UHD_MSG(warning) << + "USRP1 cannot achieve decimations below 8 when the half-band filter is present.\n" + "The usrp1_fpga_4rx.rbf file is a special FPGA image without RX half-band filters.\n" + "To load this image, set the device address key/value pair: fpga=usrp1_fpga_4rx.rbf\n" + << std::endl; + + if (dspno == 0){ //only care if dsp0 is set since its homogeneous + bool s = this->disable_rx(); + _iface->poke32(FR_RX_SAMPLE_RATE_DIV, div - 1); + _iface->poke32(FR_DECIM_RATE, rate/div - 1); + this->restore_rx(s); + + //update the streamer if created + boost::shared_ptr<usrp1_recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<usrp1_recv_packet_streamer>(_rx_streamer.lock()); + if (my_streamer.get() != NULL){ + my_streamer->set_samp_rate(_master_clock_rate / rate); + } + } + + return _master_clock_rate / rate; +} + +double usrp1_impl::update_tx_samp_rate(size_t dspno, const double samp_rate){ + + const size_t div = this->has_tx_halfband()? 4 : 2; //doubled for codec interp + const size_t rate = boost::math::iround(_master_clock_rate/this->get_tx_dsp_host_rates().clip(samp_rate, true)); + + if (dspno == 0){ //only care if dsp0 is set since its homogeneous + bool s = this->disable_tx(); + _iface->poke32(FR_TX_SAMPLE_RATE_DIV, div - 1); + _iface->poke32(FR_INTERP_RATE, rate/div - 1); + this->restore_tx(s); + + //update the streamer if created + boost::shared_ptr<usrp1_send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<usrp1_send_packet_streamer>(_tx_streamer.lock()); + if (my_streamer.get() != NULL){ + my_streamer->set_samp_rate(_master_clock_rate / rate); + } + } + + return _master_clock_rate / rate; +} + +void usrp1_impl::update_rates(void){ + const fs_path mb_path = "/mboards/0"; + this->update_tick_rate(_master_clock_rate); + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ + _tree->access<double>(mb_path / "rx_dsps" / name / "rate" / "value").update(); + } + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ + _tree->access<double>(mb_path / "tx_dsps" / name / "rate" / "value").update(); + } +} + +double usrp1_impl::update_rx_dsp_freq(const size_t dspno, const double freq_){ + + //correct for outside of rate (wrap around) + double freq = std::fmod(freq_, _master_clock_rate); + if (std::abs(freq) > _master_clock_rate/2.0) + freq -= boost::math::sign(freq)*_master_clock_rate; + + //calculate the freq register word (signed) + UHD_ASSERT_THROW(std::abs(freq) <= _master_clock_rate/2.0); + static const double scale_factor = std::pow(2.0, 32); + const boost::int32_t freq_word = boost::int32_t(boost::math::round((freq / _master_clock_rate) * scale_factor)); + + static const boost::uint32_t dsp_index_to_reg_val[4] = { + FR_RX_FREQ_0, FR_RX_FREQ_1, FR_RX_FREQ_2, FR_RX_FREQ_3 + }; + _iface->poke32(dsp_index_to_reg_val[dspno], freq_word); + + return (double(freq_word) / scale_factor) * _master_clock_rate; +} + +double usrp1_impl::update_tx_dsp_freq(const size_t dspno, const double freq){ + const subdev_spec_pair_t pair = _tx_subdev_spec.at(dspno); + + //determine the connection type and hence, the sign + const std::string conn = _tree->access<std::string>(str(boost::format( + "/mboards/0/dboards/%s/tx_frontends/%s/connection" + ) % pair.db_name % pair.sd_name)).get(); + double sign = (conn == "I" or conn == "IQ")? +1.0 : -1.0; + + //map this DSP's subdev spec to a particular codec chip + _dbc[pair.db_name].codec->set_duc_freq(sign*freq, _master_clock_rate); + return freq; //assume infinite precision +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool usrp1_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +){ + boost::this_thread::disable_interruption di; //disable because the wait can throw + return _soft_time_ctrl->get_async_queue().pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr usrp1_impl::get_rx_stream(const uhd::stream_args_t &args_){ + stream_args_t args = args_; + + //setup defaults for unspecified values + args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; + args.channels.clear(); //NOTE: we have no choice about the channel mapping + for (size_t ch = 0; ch < _rx_subdev_spec.size(); ch++){ + args.channels.push_back(ch); + } + + if (args.otw_format == "sc16"){ + _iface->poke32(FR_RX_FORMAT, 0 + | (0 << bmFR_RX_FORMAT_SHIFT_SHIFT) + | (16 << bmFR_RX_FORMAT_WIDTH_SHIFT) + | bmFR_RX_FORMAT_WANT_Q + ); + } + else if (args.otw_format == "sc8"){ + _iface->poke32(FR_RX_FORMAT, 0 + | (8 << bmFR_RX_FORMAT_SHIFT_SHIFT) + | (8 << bmFR_RX_FORMAT_WIDTH_SHIFT) + | bmFR_RX_FORMAT_WANT_Q + ); + } + else{ + throw uhd::value_error("USRP1 RX cannot handle requested wire format: " + args.otw_format); + } + + //calculate packet size + const size_t bpp = _data_transport->get_recv_frame_size()/args.channels.size(); + const size_t spp = bpp/convert::get_bytes_per_item(args.otw_format); + + //make the new streamer given the samples per packet + boost::shared_ptr<usrp1_recv_packet_streamer> my_streamer = + boost::make_shared<usrp1_recv_packet_streamer>(spp, _soft_time_ctrl); + + //init some streamer stuff + my_streamer->set_tick_rate(_master_clock_rate); + my_streamer->set_vrt_unpacker(&usrp1_bs_vrt_unpacker); + my_streamer->set_xport_chan_get_buff(0, boost::bind( + &uhd::transport::zero_copy_if::get_recv_buff, _io_impl->data_transport, _1 + )); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item16_usrp1"; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = args.channels.size(); + my_streamer->set_converter(id); + + //special scale factor change for sc8 + if (args.otw_format == "sc8") + my_streamer->set_scale_factor(1.0/127); + + //save as weak ptr for update access + _rx_streamer = my_streamer; + + //sets all tick and samp rates on this streamer + this->update_rates(); + + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr usrp1_impl::get_tx_stream(const uhd::stream_args_t &args_){ + stream_args_t args = args_; + + //setup defaults for unspecified values + args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; + args.channels.clear(); //NOTE: we have no choice about the channel mapping + for (size_t ch = 0; ch < _tx_subdev_spec.size(); ch++){ + args.channels.push_back(ch); + } + + if (args.otw_format != "sc16"){ + throw uhd::value_error("USRP1 TX cannot handle requested wire format: " + args.otw_format); + } + + _iface->poke32(FR_TX_FORMAT, bmFR_TX_FORMAT_16_IQ); + + //calculate packet size + size_t bpp = _data_transport->get_send_frame_size()/args.channels.size(); + bpp -= alignment_padding - 1; //minus the max remainder after LUT commit + const size_t spp = bpp/convert::get_bytes_per_item(args.otw_format); + + //make the new streamer given the samples per packet + boost::function<void(bool)> tx_fcn = boost::bind(&usrp1_impl::tx_stream_on_off, this, _1); + boost::shared_ptr<usrp1_send_packet_streamer> my_streamer = + boost::make_shared<usrp1_send_packet_streamer>(spp, _soft_time_ctrl, tx_fcn); + + //init some streamer stuff + my_streamer->set_tick_rate(_master_clock_rate); + my_streamer->set_vrt_packer(&usrp1_bs_vrt_packer); + my_streamer->set_xport_chan_get_buff(0, boost::bind( + &usrp1_impl::io_impl::get_send_buff, _io_impl.get(), _1 + )); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = args.channels.size(); + id.output_format = args.otw_format + "_item16_usrp1"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //save as weak ptr for update access + _tx_streamer = my_streamer; + + //sets all tick and samp rates on this streamer + this->update_rates(); + + return my_streamer; +} diff --git a/host/lib/usrp/usrp1/soft_time_ctrl.cpp b/host/lib/usrp/usrp1/soft_time_ctrl.cpp new file mode 100644 index 000000000..90b3a92da --- /dev/null +++ b/host/lib/usrp/usrp1/soft_time_ctrl.cpp @@ -0,0 +1,227 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "soft_time_ctrl.hpp" +#include <uhd/utils/tasks.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/condition_variable.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace pt = boost::posix_time; + +static const time_spec_t TWIDDLE(0.0011); + +/*********************************************************************** + * Soft time control implementation + **********************************************************************/ +class soft_time_ctrl_impl : public soft_time_ctrl{ +public: + + soft_time_ctrl_impl(const cb_fcn_type &stream_on_off): + _nsamps_remaining(0), + _stream_mode(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS), + _cmd_queue(2), + _async_msg_queue(1000), + _inline_msg_queue(1000), + _stream_on_off(stream_on_off) + { + //synchronously spawn a new thread + _recv_cmd_task = task::make(boost::bind(&soft_time_ctrl_impl::recv_cmd_task, this)); + + //initialize the time to something + this->set_time(time_spec_t(0.0)); + } + + /******************************************************************* + * Time control + ******************************************************************/ + void set_time(const time_spec_t &time){ + boost::mutex::scoped_lock lock(_update_mutex); + _time_offset = time_spec_t::get_system_time() - time; + } + + time_spec_t get_time(void){ + boost::mutex::scoped_lock lock(_update_mutex); + return time_now(); + } + + UHD_INLINE time_spec_t time_now(void){ + //internal get time without scoped lock + return time_spec_t::get_system_time() - _time_offset; + } + + UHD_INLINE void sleep_until_time( + boost::mutex::scoped_lock &lock, const time_spec_t &time + ){ + boost::condition_variable cond; + //use a condition variable to unlock, sleep, lock + double seconds_to_sleep = (time - time_now()).get_real_secs(); + cond.timed_wait(lock, pt::microseconds(long(seconds_to_sleep*1e6))); + } + + /******************************************************************* + * Receive control + ******************************************************************/ + size_t recv_post(rx_metadata_t &md, const size_t nsamps){ + boost::mutex::scoped_lock lock(_update_mutex); + + //Since it timed out on the receive, check for inline messages... + //Must do a post check because recv() will not wake up for a message. + if (md.error_code == rx_metadata_t::ERROR_CODE_TIMEOUT){ + if (_inline_msg_queue.pop_with_haste(md)) return 0; + } + + //load the metadata with the expected time + md.has_time_spec = true; + md.time_spec = time_now(); + + //none of the stuff below matters in continuous streaming mode + if (_stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS) return nsamps; + + //When to stop streaming: + //The samples have been received and the stream mode is non-continuous. + //Rewrite the sample count to clip to the requested number of samples. + if (_nsamps_remaining <= nsamps) switch(_stream_mode){ + case stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE:{ + rx_metadata_t metadata; + metadata.has_time_spec = true; + metadata.time_spec = this->time_now(); + metadata.error_code = rx_metadata_t::ERROR_CODE_BROKEN_CHAIN; + _inline_msg_queue.push_with_pop_on_full(metadata); + } //continue to next case... + case stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE: + md.end_of_burst = true; + this->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + return _nsamps_remaining; + default: break; + } + + //update the consumed samples + _nsamps_remaining -= nsamps; + return nsamps; + } + + void issue_stream_cmd(const stream_cmd_t &cmd){ + _cmd_queue.push_with_wait(boost::make_shared<stream_cmd_t>(cmd)); + } + + void stream_on_off(bool enb){ + _stream_on_off(enb); + _nsamps_remaining = 0; + } + + /******************************************************************* + * Transmit control + ******************************************************************/ + void send_pre(const tx_metadata_t &md, double &timeout){ + if (not md.has_time_spec) return; + + boost::mutex::scoped_lock lock(_update_mutex); + + time_spec_t time_at(md.time_spec - TWIDDLE); + + //handle late packets + if (time_at < time_now()){ + async_metadata_t metadata; + metadata.channel = 0; + metadata.has_time_spec = true; + metadata.time_spec = this->time_now(); + metadata.event_code = async_metadata_t::EVENT_CODE_TIME_ERROR; + _async_msg_queue.push_with_pop_on_full(metadata); + return; + } + + timeout -= (time_at - time_now()).get_real_secs(); + sleep_until_time(lock, time_at); + } + + /******************************************************************* + * Thread control + ******************************************************************/ + void recv_cmd_handle_cmd(const stream_cmd_t &cmd){ + boost::mutex::scoped_lock lock(_update_mutex); + + //handle the stream at time by sleeping + if (not cmd.stream_now){ + time_spec_t time_at(cmd.time_spec - TWIDDLE); + if (time_at < time_now()){ + rx_metadata_t metadata; + metadata.has_time_spec = true; + metadata.time_spec = this->time_now(); + metadata.error_code = rx_metadata_t::ERROR_CODE_LATE_COMMAND; + _inline_msg_queue.push_with_pop_on_full(metadata); + this->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + return; + } + else{ + sleep_until_time(lock, time_at); + } + } + + //When to stop streaming: + //Stop streaming when the command is a stop and streaming. + if (cmd.stream_mode == stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS + and _stream_mode != stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS + ) stream_on_off(false); + + //When to start streaming: + //Start streaming when the command is not a stop and not streaming. + if (cmd.stream_mode != stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS + and _stream_mode == stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS + ) stream_on_off(true); + + //update the state + _nsamps_remaining += cmd.num_samps; + _stream_mode = cmd.stream_mode; + } + + void recv_cmd_task(void){ //task is looped + boost::shared_ptr<stream_cmd_t> cmd; + _cmd_queue.pop_with_wait(cmd); + recv_cmd_handle_cmd(*cmd); + } + + bounded_buffer<async_metadata_t> &get_async_queue(void){ + return _async_msg_queue; + } + + bounded_buffer<rx_metadata_t> &get_inline_queue(void){ + return _inline_msg_queue; + } + +private: + boost::mutex _update_mutex; + size_t _nsamps_remaining; + stream_cmd_t::stream_mode_t _stream_mode; + time_spec_t _time_offset; + bounded_buffer<boost::shared_ptr<stream_cmd_t> > _cmd_queue; + bounded_buffer<async_metadata_t> _async_msg_queue; + bounded_buffer<rx_metadata_t> _inline_msg_queue; + const cb_fcn_type _stream_on_off; + task::sptr _recv_cmd_task; +}; + +/*********************************************************************** + * Soft time control factor + **********************************************************************/ +soft_time_ctrl::sptr soft_time_ctrl::make(const cb_fcn_type &stream_on_off){ + return sptr(new soft_time_ctrl_impl(stream_on_off)); +} diff --git a/host/lib/usrp/usrp1/soft_time_ctrl.hpp b/host/lib/usrp/usrp1/soft_time_ctrl.hpp new file mode 100644 index 000000000..b92b51252 --- /dev/null +++ b/host/lib/usrp/usrp1/soft_time_ctrl.hpp @@ -0,0 +1,74 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USRP1_SOFT_TIME_CTRL_HPP +#define INCLUDED_LIBUHD_USRP_USRP1_SOFT_TIME_CTRL_HPP + +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> + +namespace uhd{ namespace usrp{ + +/*! + * The soft time control emulates some of the + * advanced streaming capabilities of the later USRP models. + * Soft time control uses the system time to emulate + * timed transmits, timed receive commands, device time, + * and inline and async error messages. + */ +class soft_time_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<soft_time_ctrl> sptr; + typedef boost::function<void(bool)> cb_fcn_type; + + /*! + * Make a new soft time control. + * \param stream_on_off a function to enable/disable rx + * \return a new soft time control object + */ + static sptr make(const cb_fcn_type &stream_on_off); + + //! Set the current time + virtual void set_time(const time_spec_t &time) = 0; + + //! Get the current time + virtual time_spec_t get_time(void) = 0; + + //! Call after the internal recv function + virtual size_t recv_post(rx_metadata_t &md, const size_t nsamps) = 0; + + //! Call before the internal send function + virtual void send_pre(const tx_metadata_t &md, double &timeout) = 0; + + //! Issue a stream command to receive + virtual void issue_stream_cmd(const stream_cmd_t &cmd) = 0; + + //! Get access to a buffer of async metadata + virtual transport::bounded_buffer<async_metadata_t> &get_async_queue(void) = 0; + + //! Get access to a buffer of inline metadata + virtual transport::bounded_buffer<rx_metadata_t> &get_inline_queue(void) = 0; +}; + +}} //namespace + +#endif /* INCLUDED_LIBUHD_USRP_USRP1_SOFT_TIME_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_calc_mux.hpp b/host/lib/usrp/usrp1/usrp1_calc_mux.hpp new file mode 100644 index 000000000..d86a7a809 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_calc_mux.hpp @@ -0,0 +1,156 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <utility> +#include <vector> +#include <string> + +#ifndef INCLUDED_USRP1_CALC_MUX_HPP +#define INCLUDED_USRP1_CALC_MUX_HPP + +//db_name, conn_type for the mux calculations below... +typedef std::pair<std::string, std::string> mapping_pair_t; + +/*********************************************************************** + * Calculate the RX mux value: + * The I and Q mux values are intentionally reversed to flip I and Q + * to account for the reversal in the type conversion routines. + **********************************************************************/ +static int calc_rx_mux_pair(int adc_for_i, int adc_for_q){ + return (adc_for_i << 0) | (adc_for_q << 2); +} + +/*! + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------+-------+-------+-------+-------+-+-----+ + * | must be zero | Q3| I3| Q2| I2| Q1| I1| Q0| I0|Z| NCH | + * +-----------------------+-------+-------+-------+-------+-+-----+ + */ +static boost::uint32_t calc_rx_mux(const std::vector<mapping_pair_t> &mapping){ + //create look-up-table for mapping dboard name and connection type to ADC flags + static const int ADC0 = 0, ADC1 = 1, ADC2 = 2, ADC3 = 3; + static const uhd::dict<std::string, uhd::dict<std::string, int> > name_to_conn_to_flag = boost::assign::map_list_of + ("A", boost::assign::map_list_of + ("IQ", calc_rx_mux_pair(ADC0, ADC1)) //I and Q + ("QI", calc_rx_mux_pair(ADC1, ADC0)) //I and Q + ("I", calc_rx_mux_pair(ADC0, ADC0)) //I and Q (Q identical but ignored Z=1) + ("Q", calc_rx_mux_pair(ADC1, ADC1)) //I and Q (Q identical but ignored Z=1) + ) + ("B", boost::assign::map_list_of + ("IQ", calc_rx_mux_pair(ADC2, ADC3)) //I and Q + ("QI", calc_rx_mux_pair(ADC3, ADC2)) //I and Q + ("I", calc_rx_mux_pair(ADC2, ADC2)) //I and Q (Q identical but ignored Z=1) + ("Q", calc_rx_mux_pair(ADC3, ADC3)) //I and Q (Q identical but ignored Z=1) + ) + ; + + //extract the number of channels + const size_t nchan = mapping.size(); + + //calculate the channel flags + int channel_flags = 0; + size_t num_reals = 0, num_quads = 0; + BOOST_FOREACH(const mapping_pair_t &pair, uhd::reversed(mapping)){ + const std::string name = pair.first, conn = pair.second; + if (conn == "IQ" or conn == "QI") num_quads++; + if (conn == "I" or conn == "Q") num_reals++; + channel_flags = (channel_flags << 4) | name_to_conn_to_flag[name][conn]; + } + + //calculate Z: + // for all real sources: Z = 1 + // for all quadrature sources: Z = 0 + // for mixed sources: warning + Z = 0 + int Z = (num_quads > 0)? 0 : 1; + if (num_quads != 0 and num_reals != 0) UHD_MSG(warning) << boost::format( + "Mixing real and quadrature rx subdevices is not supported.\n" + "The Q input to the real source(s) will be non-zero.\n" + ); + + //calculate the rx mux value + return ((channel_flags & 0xffff) << 4) | ((Z & 0x1) << 3) | ((nchan & 0x7) << 0); +} + +/*********************************************************************** + * Calculate the TX mux value: + * The I and Q mux values are intentionally reversed to flip I and Q + * to account for the reversal in the type conversion routines. + **********************************************************************/ +static int calc_tx_mux_pair(int chn_for_i, int chn_for_q){ + return (chn_for_i << 0) | (chn_for_q << 4); +} + +/*! + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------+-------+-------+-------+-------+-+-----+ + * | | DAC1Q | DAC1I | DAC0Q | DAC0I |0| NCH | + * +-----------------------------------------------+-------+-+-----+ + */ +static boost::uint32_t calc_tx_mux(const std::vector<mapping_pair_t> &mapping){ + //create look-up-table for mapping channel number and connection type to flags + static const int ENB = 1 << 3, CHAN_I0 = 0, CHAN_Q0 = 1, CHAN_I1 = 2, CHAN_Q1 = 3; + static const uhd::dict<size_t, uhd::dict<std::string, int> > chan_to_conn_to_flag = boost::assign::map_list_of + (0, boost::assign::map_list_of + ("IQ", calc_tx_mux_pair(CHAN_I0 | ENB, CHAN_Q0 | ENB)) + ("QI", calc_tx_mux_pair(CHAN_Q0 | ENB, CHAN_I0 | ENB)) + ("I", calc_tx_mux_pair(CHAN_I0 | ENB, 0 )) + ("Q", calc_tx_mux_pair(0, CHAN_I0 | ENB)) + ) + (1, boost::assign::map_list_of + ("IQ", calc_tx_mux_pair(CHAN_I1 | ENB, CHAN_Q1 | ENB)) + ("QI", calc_tx_mux_pair(CHAN_Q1 | ENB, CHAN_I1 | ENB)) + ("I", calc_tx_mux_pair(CHAN_I1 | ENB, 0 )) + ("Q", calc_tx_mux_pair(0, CHAN_I1 | ENB)) + ) + ; + + //extract the number of channels + size_t nchan = mapping.size(); + + //calculate the channel flags + int channel_flags = 0, chan = 0; + uhd::dict<std::string, int> slot_to_chan_count = boost::assign::map_list_of("A", 0)("B", 0); + BOOST_FOREACH(const mapping_pair_t &pair, mapping){ + const std::string name = pair.first, conn = pair.second; + + //combine the channel flags: shift for slot A vs B + if (name == "A") channel_flags |= chan_to_conn_to_flag[chan][conn] << 0; + if (name == "B") channel_flags |= chan_to_conn_to_flag[chan][conn] << 8; + + //sanity check, only 1 channel per slot + slot_to_chan_count[name]++; + if (slot_to_chan_count[name] > 1) throw uhd::value_error( + "cannot assign dboard slot to multiple channels: " + name + ); + + //increment for the next channel + chan++; + } + + //calculate the tx mux value + return ((channel_flags & 0xffff) << 4) | ((nchan & 0x7) << 0); +} + +#endif /* INCLUDED_USRP1_CALC_MUX_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_iface.cpp b/host/lib/usrp/usrp1/usrp1_iface.cpp new file mode 100644 index 000000000..c790aecb4 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_iface.cpp @@ -0,0 +1,205 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_iface.hpp" +#include "usrp_commands.h" +#include <uhd/utils/log.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/format.hpp> +#include <stdexcept> +#include <iomanip> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +class usrp1_iface_impl : public usrp1_iface{ +public: + /******************************************************************* + * Structors + ******************************************************************/ + usrp1_iface_impl(uhd::usrp::fx2_ctrl::sptr ctrl_transport) + { + _ctrl_transport = ctrl_transport; + } + + ~usrp1_iface_impl(void) + { + /* NOP */ + } + + /******************************************************************* + * Peek and Poke + ******************************************************************/ + void poke32(boost::uint32_t addr, boost::uint32_t value) + { + boost::uint32_t swapped = uhd::htonx(value); + + UHD_LOGV(always) + << "poke32(" + << std::dec << std::setw(2) << addr << ", 0x" + << std::hex << std::setw(8) << value << ")" << std::endl + ; + + boost::uint8_t w_index_h = SPI_ENABLE_FPGA & 0xff; + boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff; + + int ret =_ctrl_transport->usrp_control_write( + VRQ_SPI_WRITE, + addr & 0x7f, + (w_index_h << 8) | (w_index_l << 0), + (unsigned char*) &swapped, + sizeof(boost::uint32_t)); + + if (ret < 0) throw uhd::io_error("USRP1: failed control write"); + } + + boost::uint32_t peek32(boost::uint32_t addr) + { + UHD_LOGV(always) + << "peek32(" + << std::dec << std::setw(2) << addr << ")" << std::endl + ; + + boost::uint32_t value_out; + + boost::uint8_t w_index_h = SPI_ENABLE_FPGA & 0xff; + boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff; + + int ret = _ctrl_transport->usrp_control_read( + VRQ_SPI_READ, + 0x80 | (addr & 0x7f), + (w_index_h << 8) | (w_index_l << 0), + (unsigned char*) &value_out, + sizeof(boost::uint32_t)); + + if (ret < 0) throw uhd::io_error("USRP1: failed control read"); + + return uhd::ntohx(value_out); + } + + void poke16(boost::uint32_t, boost::uint16_t) { + throw uhd::not_implemented_error("Unhandled command poke16()"); + } + + boost::uint16_t peek16(boost::uint32_t) { + throw uhd::not_implemented_error("Unhandled command peek16()"); + return 0; + } + + /******************************************************************* + * I2C + ******************************************************************/ + void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ + return _ctrl_transport->write_i2c(addr, bytes); + } + + byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ + return _ctrl_transport->read_i2c(addr, num_bytes); + } + + /******************************************************************* + * SPI + * + * For non-readback transactions use the SPI_WRITE command, which is + * simpler and uses the USB control buffer for OUT data. No data + * needs to be returned. + * + * For readback transactions use SPI_TRANSACT, which places up to + * 4 bytes of OUT data in the device request fields and uses the + * control buffer for IN data. + ******************************************************************/ + boost::uint32_t transact_spi(int which_slave, + const spi_config_t &, + boost::uint32_t bits, + size_t num_bits, + bool readback) + { + UHD_LOGV(always) + << "transact_spi: " << std::endl + << " slave: " << which_slave << std::endl + << " bits: " << bits << std::endl + << " num_bits: " << num_bits << std::endl + << " readback: " << readback << std::endl + ; + UHD_ASSERT_THROW((num_bits <= 32) && !(num_bits % 8)); + size_t num_bytes = num_bits / 8; + + if (readback) { + unsigned char buff[4] = { + (bits >> 0) & 0xff, (bits >> 8) & 0xff, + (bits >> 16) & 0xff, (bits >> 24) & 0xff + }; + //conditions where there are two header bytes + if (num_bytes >= 3 and buff[num_bytes-1] != 0 and buff[num_bytes-2] != 0 and buff[num_bytes-3] == 0){ + if (int(num_bytes-2) != _ctrl_transport->usrp_control_read( + VRQ_SPI_READ, (buff[num_bytes-1] << 8) | (buff[num_bytes-2] << 0), + (which_slave << 8) | SPI_FMT_MSB | SPI_FMT_HDR_2, + buff, num_bytes-2 + )) throw uhd::io_error("USRP1: failed SPI readback transaction"); + } + + //conditions where there is one header byte + else if (num_bytes >= 2 and buff[num_bytes-1] != 0 and buff[num_bytes-2] == 0){ + if (int(num_bytes-1) != _ctrl_transport->usrp_control_read( + VRQ_SPI_READ, buff[num_bytes-1], + (which_slave << 8) | SPI_FMT_MSB | SPI_FMT_HDR_1, + buff, num_bytes-1 + )) throw uhd::io_error("USRP1: failed SPI readback transaction"); + } + else{ + throw uhd::io_error("USRP1: invalid input data for SPI readback"); + } + boost::uint32_t val = (((boost::uint32_t)buff[0]) << 0) | + (((boost::uint32_t)buff[1]) << 8) | + (((boost::uint32_t)buff[2]) << 16) | + (((boost::uint32_t)buff[3]) << 24); + return val; + } + else { + // Byteswap on num_bytes + unsigned char buff[4] = { 0 }; + for (size_t i = 1; i <= num_bytes; i++) + buff[num_bytes - i] = (bits >> ((i - 1) * 8)) & 0xff; + + boost::uint8_t w_index_h = which_slave & 0xff; + boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_0) & 0xff; + + int ret =_ctrl_transport->usrp_control_write( + VRQ_SPI_WRITE, + 0x00, + (w_index_h << 8) | (w_index_l << 0), + buff, num_bytes); + + if (ret < 0) throw uhd::io_error("USRP1: failed SPI transaction"); + + return 0; + } + } + +private: + uhd::usrp::fx2_ctrl::sptr _ctrl_transport; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +usrp1_iface::sptr usrp1_iface::make(uhd::usrp::fx2_ctrl::sptr ctrl_transport) +{ + return sptr(new usrp1_iface_impl(ctrl_transport)); +} diff --git a/host/lib/usrp/usrp1/usrp1_iface.hpp b/host/lib/usrp/usrp1/usrp1_iface.hpp new file mode 100644 index 000000000..c1ac34f25 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_iface.hpp @@ -0,0 +1,44 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP1_IFACE_HPP +#define INCLUDED_USRP1_IFACE_HPP + +#include "fx2_ctrl.hpp" +#include "wb_iface.hpp" +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp1 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp1_iface : public wb_iface, public uhd::i2c_iface, public uhd::spi_iface, boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp1_iface> sptr; + + /*! + * Make a new usrp1 interface with the control transport. + * \param ctrl_transport the usrp controller object + * \return a new usrp1 interface object + */ + static sptr make(uhd::usrp::fx2_ctrl::sptr ctrl_transport); +}; + +#endif /* INCLUDED_USRP1_IFACE_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp new file mode 100644 index 000000000..607badda9 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -0,0 +1,502 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_impl.hpp" +#include "usrp_spi_defs.h" +#include "usrp_commands.h" +#include "fpga_regs_standard.h" +#include "fpga_regs_common.h" +#include "usrp_i2c_addr.h" +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/transport/usb_control.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/math/special_functions/round.hpp> +#include <cstdio> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +const boost::uint16_t USRP1_VENDOR_ID = 0xfffe; +const boost::uint16_t USRP1_PRODUCT_ID = 0x0002; +const boost::uint16_t FX2_VENDOR_ID = 0x04b4; +const boost::uint16_t FX2_PRODUCT_ID = 0x8613; +static const boost::posix_time::milliseconds REENUMERATION_TIMEOUT_MS(3000); + +const std::vector<usrp1_impl::dboard_slot_t> usrp1_impl::_dboard_slots = boost::assign::list_of + (usrp1_impl::DBOARD_SLOT_A)(usrp1_impl::DBOARD_SLOT_B) +; + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t usrp1_find(const device_addr_t &hint) +{ + device_addrs_t usrp1_addrs; + + //return an empty list of addresses when type is set to non-usrp1 + if (hint.has_key("type") and hint["type"] != "usrp1") return usrp1_addrs; + + //Return an empty list of addresses when an address is specified, + //since an address is intended for a different, non-USB, device. + if (hint.has_key("addr")) return usrp1_addrs; + + unsigned int vid, pid; + + if(hint.has_key("vid") && hint.has_key("pid") && hint.has_key("type") && hint["type"] == "usrp1") { + sscanf(hint.get("vid").c_str(), "%x", &vid); + sscanf(hint.get("pid").c_str(), "%x", &pid); + } else { + vid = USRP1_VENDOR_ID; + pid = USRP1_PRODUCT_ID; + } + + // Important note: + // The get device list calls are nested inside the for loop. + // This allows the usb guts to decontruct when not in use, + // so that re-enumeration after fw load can occur successfully. + // This requirement is a courtesy of libusb1.0 on windows. + + //find the usrps and load firmware + size_t found = 0; + BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { + //extract the firmware path for the USRP1 + std::string usrp1_fw_image; + try{ + usrp1_fw_image = find_image_path(hint.get("fw", "usrp1_fw.ihx")); + } + catch(...){ + UHD_MSG(warning) << boost::format( + "Could not locate USRP1 firmware.\n" + "Please install the images package.\n" + ); + return usrp1_addrs; + } + UHD_LOG << "USRP1 firmware image: " << usrp1_fw_image << std::endl; + + usb_control::sptr control; + try{control = usb_control::make(handle, 0);} + catch(const uhd::exception &){continue;} //ignore claimed + + fx2_ctrl::make(control)->usrp_load_firmware(usrp1_fw_image); + found++; + } + + //get descriptors again with serial number, but using the initialized VID/PID now since we have firmware + vid = USRP1_VENDOR_ID; + pid = USRP1_PRODUCT_ID; + + const boost::system_time timeout_time = boost::get_system_time() + REENUMERATION_TIMEOUT_MS; + + //search for the device until found or timeout + while (boost::get_system_time() < timeout_time and usrp1_addrs.empty() and found != 0) + { + BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) + { + usb_control::sptr control; + try{control = usb_control::make(handle, 0);} + catch(const uhd::exception &){continue;} //ignore claimed + + fx2_ctrl::sptr fx2_ctrl = fx2_ctrl::make(control); + const mboard_eeprom_t mb_eeprom(*fx2_ctrl, mboard_eeprom_t::MAP_B000); + device_addr_t new_addr; + new_addr["type"] = "usrp1"; + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = handle->get_serial(); + //this is a found usrp1 when the hint serial and name match or blank + if ( + (not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) + ){ + usrp1_addrs.push_back(new_addr); + } + } + } + + return usrp1_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp1_make(const device_addr_t &device_addr){ + return device::sptr(new usrp1_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_usrp1_device){ + device::register_device(&usrp1_find, &usrp1_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ + UHD_MSG(status) << "Opening a USRP1 device..." << std::endl; + + //extract the FPGA path for the USRP1 + std::string usrp1_fpga_image = find_image_path( + device_addr.get("fpga", "usrp1_fpga.rbf") + ); + UHD_LOG << "USRP1 FPGA image: " << usrp1_fpga_image << std::endl; + + //try to match the given device address with something on the USB bus + std::vector<usb_device_handle::sptr> device_list = + usb_device_handle::get_device_list(USRP1_VENDOR_ID, USRP1_PRODUCT_ID); + + //locate the matching handle in the device list + usb_device_handle::sptr handle; + BOOST_FOREACH(usb_device_handle::sptr dev_handle, device_list) { + if (dev_handle->get_serial() == device_addr["serial"]){ + handle = dev_handle; + break; + } + } + UHD_ASSERT_THROW(handle.get() != NULL); //better be found + + //////////////////////////////////////////////////////////////////// + // Create controller objects + //////////////////////////////////////////////////////////////////// + //usb_control::sptr usb_ctrl = usb_control::make(handle); + _fx2_ctrl = fx2_ctrl::make(usb_control::make(handle, 0)); + _fx2_ctrl->usrp_load_fpga(usrp1_fpga_image); + _fx2_ctrl->usrp_init(); + _data_transport = usb_zero_copy::make( + handle, // identifier + 2, 6, // IN interface, endpoint + 1, 2, // OUT interface, endpoint + device_addr // param hints + ); + _iface = usrp1_iface::make(_fx2_ctrl); + _soft_time_ctrl = soft_time_ctrl::make( + boost::bind(&usrp1_impl::rx_stream_on_off, this, _1) + ); + _dbc["A"]; _dbc["B"]; //ensure that keys exist + + // Normal mode with no loopback or Rx counting + _iface->poke32(FR_MODE, 0x00000000); + _iface->poke32(FR_DEBUG_EN, 0x00000000); + + UHD_LOG + << "USRP1 Capabilities" << std::endl + << " number of duc's: " << get_num_ddcs() << std::endl + << " number of ddc's: " << get_num_ducs() << std::endl + << " rx halfband: " << has_rx_halfband() << std::endl + << " tx halfband: " << has_tx_halfband() << std::endl + ; + + //////////////////////////////////////////////////////////////////// + // Initialize the properties tree + //////////////////////////////////////////////////////////////////// + _rx_dc_offset_shadow = 0; + _tree = property_tree::make(); + _tree->create<std::string>("/name").set("USRP1 Device"); + const fs_path mb_path = "/mboards/0"; + _tree->create<std::string>(mb_path / "name").set("USRP1 (Classic)"); + _tree->create<std::string>(mb_path / "load_eeprom") + .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + + //////////////////////////////////////////////////////////////////// + // setup the mboard eeprom + //////////////////////////////////////////////////////////////////// + const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, mboard_eeprom_t::MAP_B000); + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(mb_eeprom) + .subscribe(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1)); + + //////////////////////////////////////////////////////////////////// + // create clock control objects + //////////////////////////////////////////////////////////////////// + _master_clock_rate = 64e6; + if (device_addr.has_key("mcr")){ + try{ + _master_clock_rate = boost::lexical_cast<double>(device_addr["mcr"]); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Error parsing FPGA clock rate from device address: " << e.what() << std::endl; + } + } + else if (not mb_eeprom["mcr"].empty()){ + try{ + _master_clock_rate = boost::lexical_cast<double>(mb_eeprom["mcr"]); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Error parsing FPGA clock rate from EEPROM: " << e.what() << std::endl; + } + } + UHD_MSG(status) << boost::format("Using FPGA clock rate of %fMHz...") % (_master_clock_rate/1e6) << std::endl; + _tree->create<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&usrp1_impl::update_tick_rate, this, _1)) + .set(_master_clock_rate); + + //////////////////////////////////////////////////////////////////// + // create codec control objects + //////////////////////////////////////////////////////////////////// + BOOST_FOREACH(const std::string &db, _dbc.keys()){ + _dbc[db].codec = usrp1_codec_ctrl::make(_iface, (db == "A")? SPI_ENABLE_CODEC_A : SPI_ENABLE_CODEC_B); + const fs_path rx_codec_path = mb_path / "rx_codecs" / db; + const fs_path tx_codec_path = mb_path / "tx_codecs" / db; + _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); + _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::rx_pga_gain_range); + _tree->create<double>(rx_codec_path / "gains/pga/value") + .coerce(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1)); + _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); + _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::tx_pga_gain_range); + _tree->create<double>(tx_codec_path / "gains/pga/value") + .subscribe(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1)) + .publish(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec)); + } + + //////////////////////////////////////////////////////////////////// + // and do the misc mboard sensors + //////////////////////////////////////////////////////////////////// + //none for now... + _tree->create<int>(mb_path / "sensors"); //phony property so this dir exists + + //////////////////////////////////////////////////////////////////// + // create frontend control objects + //////////////////////////////////////////////////////////////////// + _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") + .subscribe(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1)); + _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") + .subscribe(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1)); + + BOOST_FOREACH(const std::string &db, _dbc.keys()){ + const fs_path rx_fe_path = mb_path / "rx_frontends" / db; + _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1)) + .set(std::complex<double>(0.0, 0.0)); + _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") + .subscribe(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1)) + .set(true); + } + + //////////////////////////////////////////////////////////////////// + // create rx dsp control objects + //////////////////////////////////////////////////////////////////// + _tree->create<int>(mb_path / "rx_dsps"); //dummy in case we have none + for (size_t dspno = 0; dspno < get_num_ddcs(); dspno++){ + fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); + _tree->create<meta_range_t>(rx_dsp_path / "rate/range") + .publish(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this)); + _tree->create<double>(rx_dsp_path / "rate/value") + .set(1e6) //some default rate + .coerce(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1)); + _tree->create<double>(rx_dsp_path / "freq/value") + .coerce(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1)); + _tree->create<meta_range_t>(rx_dsp_path / "freq/range") + .publish(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this)); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd"); + if (dspno == 0){ + //only subscribe the callback for dspno 0 since it will stream all dsps + _tree->access<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .subscribe(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1)); + } + } + + //////////////////////////////////////////////////////////////////// + // create tx dsp control objects + //////////////////////////////////////////////////////////////////// + _tree->create<int>(mb_path / "tx_dsps"); //dummy in case we have none + for (size_t dspno = 0; dspno < get_num_ducs(); dspno++){ + fs_path tx_dsp_path = mb_path / str(boost::format("tx_dsps/%u") % dspno); + _tree->create<meta_range_t>(tx_dsp_path / "rate/range") + .publish(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this)); + _tree->create<double>(tx_dsp_path / "rate/value") + .set(1e6) //some default rate + .coerce(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1)); + _tree->create<double>(tx_dsp_path / "freq/value") + .coerce(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1)); + _tree->create<meta_range_t>(tx_dsp_path / "freq/range") + .publish(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this)); + } + + //////////////////////////////////////////////////////////////////// + // create time control objects + //////////////////////////////////////////////////////////////////// + _tree->create<time_spec_t>(mb_path / "time/now") + .publish(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl)) + .subscribe(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1)); + + _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(std::vector<std::string>(1, "internal")); + _tree->create<std::vector<std::string> >(mb_path / "time_source/options").set(std::vector<std::string>(1, "none")); + _tree->create<std::string>(mb_path / "clock_source/value").set("internal"); + _tree->create<std::string>(mb_path / "time_source/value").set("none"); + + //////////////////////////////////////////////////////////////////// + // create dboard control objects + //////////////////////////////////////////////////////////////////// + BOOST_FOREACH(const std::string &db, _dbc.keys()){ + + //read the dboard eeprom to extract the dboard ids + dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; + rx_db_eeprom.load(*_fx2_ctrl, (db == "A")? (I2C_ADDR_RX_A) : (I2C_ADDR_RX_B)); + tx_db_eeprom.load(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A) : (I2C_ADDR_TX_B)); + gdb_eeprom.load(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A ^ 5) : (I2C_ADDR_TX_B ^ 5)); + + //create the properties and register subscribers + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "rx_eeprom") + .set(rx_db_eeprom) + .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1)); + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "tx_eeprom") + .set(tx_db_eeprom) + .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1)); + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "gdb_eeprom") + .set(gdb_eeprom) + .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1)); + + //create a new dboard interface and manager + _dbc[db].dboard_iface = make_dboard_iface( + _iface, _dbc[db].codec, + (db == "A")? DBOARD_SLOT_A : DBOARD_SLOT_B, + _master_clock_rate, rx_db_eeprom.id + ); + _tree->create<dboard_iface::sptr>(mb_path / "dboards" / db/ "iface").set(_dbc[db].dboard_iface); + _dbc[db].dboard_manager = dboard_manager::make( + rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, + _dbc[db].dboard_iface, _tree->subtree(mb_path / "dboards" / db) + ); + + //init the subdev specs if we have a dboard (wont leave this loop empty) + if (rx_db_eeprom.id != dboard_id_t::none() or _rx_subdev_spec.empty()){ + _rx_subdev_spec = subdev_spec_t(db + ":" + _tree->list(mb_path / "dboards" / db / "rx_frontends").at(0)); + } + if (tx_db_eeprom.id != dboard_id_t::none() or _tx_subdev_spec.empty()){ + _tx_subdev_spec = subdev_spec_t(db + ":" + _tree->list(mb_path / "dboards" / db / "tx_frontends").at(0)); + } + } + + //initialize io handling + this->io_init(); + + //////////////////////////////////////////////////////////////////// + // do some post-init tasks + //////////////////////////////////////////////////////////////////// + this->update_rates(); + + //reset cordic rates and their properties to zero + BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ + _tree->access<double>(mb_path / "rx_dsps" / name / "freq" / "value").set(0.0); + } + + if (_tree->list(mb_path / "rx_dsps").size() > 0) + _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(_rx_subdev_spec); + if (_tree->list(mb_path / "tx_dsps").size() > 0) + _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(_tx_subdev_spec); + +} + +usrp1_impl::~usrp1_impl(void){ + UHD_SAFE_CALL( + this->enable_rx(false); + this->enable_tx(false); + ) + _tree.reset(); //resets counts on sptrs held in tree + _soft_time_ctrl.reset(); //stops cmd task before proceeding + _io_impl.reset(); //stops vandal before other stuff gets deconstructed +} + +/*! + * Capabilities Register + * + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------------------------------+-+-----+-+-----+ + * | Reserved |T|DUCs |R|DDCs | + * +-----------------------------------------------+-+-----+-+-----+ + */ +size_t usrp1_impl::get_num_ddcs(void){ + boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); + return (regval >> 0) & 0x0007; +} + +size_t usrp1_impl::get_num_ducs(void){ + boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); + return (regval >> 4) & 0x0007; +} + +bool usrp1_impl::has_rx_halfband(void){ + boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); + return (regval >> 3) & 0x0001; +} + +bool usrp1_impl::has_tx_halfband(void){ + boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); + return (regval >> 7) & 0x0001; +} + +/*********************************************************************** + * Properties callback methods below + **********************************************************************/ +void usrp1_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom){ + mb_eeprom.commit(*_fx2_ctrl, mboard_eeprom_t::MAP_B000); +} + +void usrp1_impl::set_db_eeprom(const std::string &db, const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ + if (type == "rx") db_eeprom.store(*_fx2_ctrl, (db == "A")? (I2C_ADDR_RX_A) : (I2C_ADDR_RX_B)); + if (type == "tx") db_eeprom.store(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A) : (I2C_ADDR_TX_B)); + if (type == "gdb") db_eeprom.store(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A ^ 5) : (I2C_ADDR_TX_B ^ 5)); +} + +double usrp1_impl::update_rx_codec_gain(const std::string &db, const double gain){ + //set gain on both I and Q, readback on one + //TODO in the future, gains should have individual control + _dbc[db].codec->set_rx_pga_gain(gain, 'A'); + _dbc[db].codec->set_rx_pga_gain(gain, 'B'); + return _dbc[db].codec->get_rx_pga_gain('A'); +} + +uhd::meta_range_t usrp1_impl::get_rx_dsp_freq_range(void){ + return meta_range_t(-_master_clock_rate/2, +_master_clock_rate/2); +} + +uhd::meta_range_t usrp1_impl::get_tx_dsp_freq_range(void){ + //magic scalar comes from codec control: + return meta_range_t(-_master_clock_rate*0.6875, +_master_clock_rate*0.6875); +} + +void usrp1_impl::set_enb_rx_dc_offset(const std::string &db, const bool enb){ + const size_t shift = (db == "A")? 0 : 2; + _rx_dc_offset_shadow &= ~(0x3 << shift); //clear bits + _rx_dc_offset_shadow |= ((enb)? 0x3 : 0x0) << shift; + _iface->poke32(FR_DC_OFFSET_CL_EN, _rx_dc_offset_shadow & 0xf); +} + +std::complex<double> usrp1_impl::set_rx_dc_offset(const std::string &db, const std::complex<double> &offset){ + const boost::int32_t i_off = boost::math::iround(offset.real() * (1ul << 31)); + const boost::int32_t q_off = boost::math::iround(offset.imag() * (1ul << 31)); + + if (db == "A"){ + _iface->poke32(FR_ADC_OFFSET_0, i_off); + _iface->poke32(FR_ADC_OFFSET_1, q_off); + } + + if (db == "B"){ + _iface->poke32(FR_ADC_OFFSET_2, i_off); + _iface->poke32(FR_ADC_OFFSET_3, q_off); + } + + return std::complex<double>(double(i_off) * (1ul << 31), double(q_off) * (1ul << 31)); +} diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp new file mode 100644 index 000000000..fd1b8c193 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -0,0 +1,175 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_iface.hpp" +#include "codec_ctrl.hpp" +#include "soft_time_ctrl.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <boost/weak_ptr.hpp> +#include <complex> + +#ifndef INCLUDED_USRP1_IMPL_HPP +#define INCLUDED_USRP1_IMPL_HPP + +/*! + * USRP1 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class usrp1_impl : public uhd::device { +public: + //! used everywhere to differentiate slots/sides... + enum dboard_slot_t{ + DBOARD_SLOT_A = 'A', + DBOARD_SLOT_B = 'B' + }; + //and a way to enumerate through a list of the above... + static const std::vector<dboard_slot_t> _dboard_slots; + + //structors + usrp1_impl(const uhd::device_addr_t &); + ~usrp1_impl(void); + + //the io interface + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + bool recv_async_msg(uhd::async_metadata_t &, double); + +private: + uhd::property_tree::sptr _tree; + + //device properties interface + uhd::property_tree::sptr get_tree(void) const{ + return _tree; + } + + //controllers + uhd::usrp::fx2_ctrl::sptr _fx2_ctrl; + usrp1_iface::sptr _iface; + uhd::usrp::soft_time_ctrl::sptr _soft_time_ctrl; + uhd::transport::usb_zero_copy::sptr _data_transport; + struct db_container_type{ + usrp1_codec_ctrl::sptr codec; + uhd::usrp::dboard_iface::sptr dboard_iface; + uhd::usrp::dboard_manager::sptr dboard_manager; + }; + uhd::dict<std::string, db_container_type> _dbc; + + double _master_clock_rate; //clock rate shadow + + //weak pointers to streamers for update purposes + boost::weak_ptr<uhd::rx_streamer> _rx_streamer; + boost::weak_ptr<uhd::tx_streamer> _tx_streamer; + + void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); + void set_db_eeprom(const std::string &, const std::string &, const uhd::usrp::dboard_eeprom_t &); + double update_rx_codec_gain(const std::string &, const double); //sets A and B at once + void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); + double update_rx_samp_rate(size_t dspno, const double); + double update_tx_samp_rate(size_t dspno, const double); + void update_rates(void); + double update_rx_dsp_freq(const size_t, const double); + double update_tx_dsp_freq(const size_t, const double); + void update_tick_rate(const double rate); + uhd::meta_range_t get_rx_dsp_freq_range(void); + uhd::meta_range_t get_tx_dsp_freq_range(void); + uhd::meta_range_t get_rx_dsp_host_rates(void); + uhd::meta_range_t get_tx_dsp_host_rates(void); + size_t _rx_dc_offset_shadow; + void set_enb_rx_dc_offset(const std::string &db, const bool); + std::complex<double> set_rx_dc_offset(const std::string &db, const std::complex<double> &); + + static uhd::usrp::dboard_iface::sptr make_dboard_iface( + usrp1_iface::sptr, + usrp1_codec_ctrl::sptr, + dboard_slot_t, + const double &, + const uhd::usrp::dboard_id_t & + ); + + //handle io stuff + UHD_PIMPL_DECL(io_impl) _io_impl; + void io_init(void); + void rx_stream_on_off(bool); + void tx_stream_on_off(bool); + void handle_overrun(size_t); + + //channel mapping shadows + uhd::usrp::subdev_spec_t _rx_subdev_spec, _tx_subdev_spec; + + //capabilities + size_t get_num_ducs(void); + size_t get_num_ddcs(void); + bool has_rx_halfband(void); + bool has_tx_halfband(void); + + void vandal_conquest_loop(void); + + //handle the enables + bool _rx_enabled, _tx_enabled; + void enable_rx(bool enb){ + _rx_enabled = enb; + _fx2_ctrl->usrp_rx_enable(enb); + } + void enable_tx(bool enb){ + _tx_enabled = enb; + _fx2_ctrl->usrp_tx_enable(enb); + } + + //conditionally disable and enable rx + bool disable_rx(void){ + if (_rx_enabled){ + enable_rx(false); + return true; + } + return false; + } + void restore_rx(bool last){ + if (last != _rx_enabled){ + enable_rx(last); + } + } + + //conditionally disable and enable tx + bool disable_tx(void){ + if (_tx_enabled){ + enable_tx(false); + return true; + } + return false; + } + void restore_tx(bool last){ + if (last != _tx_enabled){ + enable_tx(last); + } + } +}; + +#endif /* INCLUDED_USRP1_IMPL_HPP */ diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt new file mode 100644 index 000000000..10f7407b0 --- /dev/null +++ b/host/lib/usrp/usrp2/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP2 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF) + +IF(ENABLE_USRP2) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_impl.cpp + ) +ENDIF(ENABLE_USRP2) diff --git a/host/lib/usrp/usrp2/clock_ctrl.cpp b/host/lib/usrp/usrp2/clock_ctrl.cpp new file mode 100644 index 000000000..7d3ffefa2 --- /dev/null +++ b/host/lib/usrp/usrp2/clock_ctrl.cpp @@ -0,0 +1,390 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9510_regs.hpp" +#include "usrp2_regs.hpp" //spi slave constants +#include "usrp2_clk_regs.hpp" +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/cstdint.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> + +using namespace uhd; + +static const bool enb_test_clk = false; + +/*! + * A usrp2 clock control specific to the ad9510 ic. + */ +class usrp2_clock_ctrl_impl : public usrp2_clock_ctrl{ +public: + usrp2_clock_ctrl_impl(usrp2_iface::sptr iface){ + _iface = iface; + clk_regs = usrp2_clk_regs_t(_iface->get_rev()); + + _ad9510_regs.cp_current_setting = ad9510_regs_t::CP_CURRENT_SETTING_3_0MA; + this->write_reg(clk_regs.pll_3); + + // Setup the clock registers to 100MHz: + // This was already done by the firmware (or the host couldnt communicate). + // We could remove this part, and just leave it to the firmware. + // But why not leave it in for those who want to mess with clock settings? + // 100mhz = 10mhz/R * (P*B + A) + + _ad9510_regs.pll_power_down = ad9510_regs_t::PLL_POWER_DOWN_NORMAL; + _ad9510_regs.prescaler_value = ad9510_regs_t::PRESCALER_VALUE_DIV2; + this->write_reg(clk_regs.pll_4); + + _ad9510_regs.acounter = 0; + this->write_reg(clk_regs.acounter); + + _ad9510_regs.bcounter_msb = 0; + _ad9510_regs.bcounter_lsb = 5; + this->write_reg(clk_regs.bcounter_msb); + this->write_reg(clk_regs.bcounter_lsb); + + _ad9510_regs.ref_counter_msb = 0; + _ad9510_regs.ref_counter_lsb = 1; // r divider = 1 + this->write_reg(clk_regs.ref_counter_msb); + this->write_reg(clk_regs.ref_counter_lsb); + + /* regs will be updated in commands below */ + + this->enable_external_ref(false); + this->enable_rx_dboard_clock(false); + this->enable_tx_dboard_clock(false); + this->enable_mimo_clock_out(false); + + /* private clock enables, must be set here */ + this->enable_dac_clock(true); + this->enable_adc_clock(true); + this->enable_test_clock(enb_test_clk); + } + + ~usrp2_clock_ctrl_impl(void){UHD_SAFE_CALL( + //power down clock outputs + this->enable_external_ref(false); + this->enable_rx_dboard_clock(false); + this->enable_tx_dboard_clock(false); + this->enable_dac_clock(false); + this->enable_adc_clock(false); + this->enable_mimo_clock_out(false); + this->enable_test_clock(false); + )} + + void enable_mimo_clock_out(bool enb){ + //calculate the low and high dividers + size_t divider = size_t(this->get_master_clock_rate()/10e6); + size_t high = divider/2; + size_t low = divider - high; + + switch(clk_regs.exp){ + case 2: //U2 rev 3 + _ad9510_regs.power_down_lvpecl_out2 = enb? + ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_NORMAL : + ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_SAFE_PD; + _ad9510_regs.output_level_lvpecl_out2 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT2_810MV; + //set the registers (divider - 1) + _ad9510_regs.divider_low_cycles_out2 = low - 1; + _ad9510_regs.divider_high_cycles_out2 = high - 1; + _ad9510_regs.bypass_divider_out2 = 0; + break; + + case 5: //U2 rev 4 + _ad9510_regs.power_down_lvds_cmos_out5 = enb? 0 : 1; + _ad9510_regs.lvds_cmos_select_out5 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT5_LVDS; + _ad9510_regs.output_level_lvds_out5 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT5_1_75MA; + //set the registers (divider - 1) + _ad9510_regs.divider_low_cycles_out5 = low - 1; + _ad9510_regs.divider_high_cycles_out5 = high - 1; + _ad9510_regs.bypass_divider_out5 = 0; + break; + + case 6: //U2+ + _ad9510_regs.power_down_lvds_cmos_out6 = enb? 0 : 1; + _ad9510_regs.lvds_cmos_select_out6 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT6_LVDS; + _ad9510_regs.output_level_lvds_out6 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT6_1_75MA; + //set the registers (divider - 1) + _ad9510_regs.divider_low_cycles_out6 = low - 1; + _ad9510_regs.divider_high_cycles_out6 = high - 1; + _ad9510_regs.bypass_divider_out5 = 0; + break; + + default: + break; + } + this->write_reg(clk_regs.output(clk_regs.exp)); + this->write_reg(clk_regs.div_lo(clk_regs.exp)); + this->update_regs(); + } + + //uses output clock 7 (cmos) + void enable_rx_dboard_clock(bool enb){ + switch(_iface->get_rev()) { + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + _ad9510_regs.power_down_lvds_cmos_out7 = enb? 0 : 1; + _ad9510_regs.lvds_cmos_select_out7 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT7_LVDS; + _ad9510_regs.output_level_lvds_out7 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT7_1_75MA; + this->write_reg(clk_regs.output(clk_regs.rx_db)); + this->update_regs(); + break; + default: + _ad9510_regs.power_down_lvds_cmos_out7 = enb? 0 : 1; + _ad9510_regs.lvds_cmos_select_out7 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT7_CMOS; + _ad9510_regs.output_level_lvds_out7 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT7_1_75MA; + this->write_reg(clk_regs.output(clk_regs.rx_db)); + this->update_regs(); + break; + } + } + + void set_rate_rx_dboard_clock(double rate){ + assert_has(get_rates_rx_dboard_clock(), rate, "rx dboard clock rate"); + size_t divider = size_t(get_master_clock_rate()/rate); + //bypass when the divider ratio is one + _ad9510_regs.bypass_divider_out7 = (divider == 1)? 1 : 0; + //calculate the low and high dividers + size_t high = divider/2; + size_t low = divider - high; + //set the registers (divider - 1) + _ad9510_regs.divider_low_cycles_out7 = low - 1; + _ad9510_regs.divider_high_cycles_out7 = high - 1; + //write the registers + this->write_reg(clk_regs.div_lo(clk_regs.rx_db)); + this->write_reg(clk_regs.div_hi(clk_regs.rx_db)); + this->update_regs(); + } + + std::vector<double> get_rates_rx_dboard_clock(void){ + std::vector<double> rates; + for (size_t i = 1; i <= 16+16; i++) rates.push_back(get_master_clock_rate()/i); + return rates; + } + + //uses output clock 6 (cmos) on USRP2, output clock 5 (cmos) on N200/N210 r3, + //and output clock 5 (lvds) on N200/N210 r4 + void enable_tx_dboard_clock(bool enb){ + switch(_iface->get_rev()) { + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + _ad9510_regs.power_down_lvds_cmos_out5 = enb? 0 : 1; + _ad9510_regs.lvds_cmos_select_out5 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT5_LVDS; + _ad9510_regs.output_level_lvds_out5 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT5_1_75MA; + break; + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + _ad9510_regs.power_down_lvds_cmos_out5 = enb? 0 : 1; + _ad9510_regs.lvds_cmos_select_out5 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT5_CMOS; + _ad9510_regs.output_level_lvds_out5 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT5_1_75MA; + break; + case usrp2_iface::USRP2_REV3: + case usrp2_iface::USRP2_REV4: + _ad9510_regs.power_down_lvds_cmos_out6 = enb? 0 : 1; + _ad9510_regs.lvds_cmos_select_out6 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT6_CMOS; + _ad9510_regs.output_level_lvds_out6 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT6_1_75MA; + break; + + default: + throw uhd::not_implemented_error("enable_tx_dboard_clock: unknown hardware version"); + break; + } + + this->write_reg(clk_regs.output(clk_regs.tx_db)); + this->update_regs(); + } + + void set_rate_tx_dboard_clock(double rate){ + assert_has(get_rates_tx_dboard_clock(), rate, "tx dboard clock rate"); + size_t divider = size_t(get_master_clock_rate()/rate); + //bypass when the divider ratio is one + _ad9510_regs.bypass_divider_out6 = (divider == 1)? 1 : 0; + //calculate the low and high dividers + size_t high = divider/2; + size_t low = divider - high; + + switch(clk_regs.tx_db) { + case 5: //USRP2+ + _ad9510_regs.bypass_divider_out5 = (divider == 1)? 1 : 0; + _ad9510_regs.divider_low_cycles_out5 = low - 1; + _ad9510_regs.divider_high_cycles_out5 = high - 1; + break; + case 6: //USRP2 + //bypass when the divider ratio is one + _ad9510_regs.bypass_divider_out6 = (divider == 1)? 1 : 0; + //set the registers (divider - 1) + _ad9510_regs.divider_low_cycles_out6 = low - 1; + _ad9510_regs.divider_high_cycles_out6 = high - 1; + break; + } + + //write the registers + this->write_reg(clk_regs.div_hi(clk_regs.tx_db)); + this->write_reg(clk_regs.div_lo(clk_regs.tx_db)); + this->update_regs(); + } + + std::vector<double> get_rates_tx_dboard_clock(void){ + return get_rates_rx_dboard_clock(); //same master clock, same dividers... + } + + void enable_test_clock(bool enb) { + _ad9510_regs.power_down_lvpecl_out0 = enb? + ad9510_regs_t::POWER_DOWN_LVPECL_OUT0_NORMAL : + ad9510_regs_t::POWER_DOWN_LVPECL_OUT0_SAFE_PD; + _ad9510_regs.output_level_lvpecl_out0 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT0_810MV; + _ad9510_regs.divider_low_cycles_out0 = 0; + _ad9510_regs.divider_high_cycles_out0 = 0; + _ad9510_regs.bypass_divider_out0 = 1; + this->write_reg(0x3c); + this->write_reg(0x48); + this->write_reg(0x49); + } + + /*! + * If we are to use an external reference, enable the charge pump. + * \param enb true to enable the CP + */ + void enable_external_ref(bool enb){ + _ad9510_regs.charge_pump_mode = (enb)? + ad9510_regs_t::CHARGE_PUMP_MODE_NORMAL : + ad9510_regs_t::CHARGE_PUMP_MODE_3STATE ; + _ad9510_regs.pll_mux_control = ad9510_regs_t::PLL_MUX_CONTROL_DLD_HIGH; + _ad9510_regs.pfd_polarity = ad9510_regs_t::PFD_POLARITY_POS; + this->write_reg(clk_regs.pll_2); + this->update_regs(); + } + + double get_master_clock_rate(void){ + return 100e6; + } + + void set_mimo_clock_delay(double delay) { + //delay_val is a 5-bit value (0-31) for fine control + //the equations below determine delay for a given ramp current, # of caps and fine delay register + //delay range: + //range_ns = 200*((caps+3)/i_ramp_ua)*1.3286 + //offset (zero delay): + //offset_ns = 0.34 + (1600 - i_ramp_ua)*1e-4 + ((caps-1)/ramp)*6 + //delay_ns = offset_ns + range_ns * delay / 31 + + int delay_val = boost::math::iround(delay/9.744e-9*31); + + if(delay_val == 0) { + switch(clk_regs.exp) { + case 5: + _ad9510_regs.delay_control_out5 = 1; + break; + case 6: + _ad9510_regs.delay_control_out6 = 1; + break; + default: + break; //delay not supported on U2 rev 3 + } + } else { + switch(clk_regs.exp) { + case 5: + _ad9510_regs.delay_control_out5 = 0; + _ad9510_regs.ramp_current_out5 = ad9510_regs_t::RAMP_CURRENT_OUT5_200UA; + _ad9510_regs.ramp_capacitor_out5 = ad9510_regs_t::RAMP_CAPACITOR_OUT5_4CAPS; + _ad9510_regs.delay_fine_adjust_out5 = delay_val; + this->write_reg(0x34); + this->write_reg(0x35); + this->write_reg(0x36); + break; + case 6: + _ad9510_regs.delay_control_out6 = 0; + _ad9510_regs.ramp_current_out6 = ad9510_regs_t::RAMP_CURRENT_OUT6_200UA; + _ad9510_regs.ramp_capacitor_out6 = ad9510_regs_t::RAMP_CAPACITOR_OUT6_4CAPS; + _ad9510_regs.delay_fine_adjust_out6 = delay_val; + this->write_reg(0x38); + this->write_reg(0x39); + this->write_reg(0x3A); + break; + default: + break; + } + } + } + +private: + /*! + * Write a single register to the spi regs. + * \param addr the address to write + */ + void write_reg(boost::uint8_t addr){ + boost::uint32_t data = _ad9510_regs.get_write_reg(addr); + _iface->write_spi(SPI_SS_AD9510, spi_config_t::EDGE_RISE, data, 24); + } + + /*! + * Tells the ad9510 to latch the settings into the operational registers. + */ + void update_regs(void){ + _ad9510_regs.update_registers = 1; + this->write_reg(clk_regs.update); + } + + //uses output clock 3 (pecl) + //this is the same between USRP2 and USRP2+ and doesn't get a switch statement + void enable_dac_clock(bool enb){ + _ad9510_regs.power_down_lvpecl_out3 = (enb)? + ad9510_regs_t::POWER_DOWN_LVPECL_OUT3_NORMAL : + ad9510_regs_t::POWER_DOWN_LVPECL_OUT3_SAFE_PD; + _ad9510_regs.output_level_lvpecl_out3 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT3_810MV; + _ad9510_regs.bypass_divider_out3 = 1; + this->write_reg(clk_regs.output(clk_regs.dac)); + this->write_reg(clk_regs.div_hi(clk_regs.dac)); + this->update_regs(); + } + + //uses output clock 4 (lvds) on USRP2 and output clock 2 (lvpecl) on USRP2+ + void enable_adc_clock(bool enb){ + switch(clk_regs.adc) { + case 2: + _ad9510_regs.power_down_lvpecl_out2 = enb? ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_NORMAL : ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_SAFE_PD; + _ad9510_regs.output_level_lvpecl_out2 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT2_500MV; + _ad9510_regs.bypass_divider_out2 = 1; + break; + case 4: + _ad9510_regs.power_down_lvds_cmos_out4 = enb? 0 : 1; + _ad9510_regs.lvds_cmos_select_out4 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT4_LVDS; + _ad9510_regs.output_level_lvds_out4 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT4_1_75MA; + _ad9510_regs.bypass_divider_out4 = 1; + break; + } + + this->write_reg(clk_regs.output(clk_regs.adc)); + this->write_reg(clk_regs.div_hi(clk_regs.adc)); + this->update_regs(); + } + + usrp2_iface::sptr _iface; + + usrp2_clk_regs_t clk_regs; + ad9510_regs_t _ad9510_regs; +}; + +/*********************************************************************** + * Public make function for the ad9510 clock control + **********************************************************************/ +usrp2_clock_ctrl::sptr usrp2_clock_ctrl::make(usrp2_iface::sptr iface){ + return sptr(new usrp2_clock_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp2/clock_ctrl.hpp b/host/lib/usrp/usrp2/clock_ctrl.hpp new file mode 100644 index 000000000..9ccbc959e --- /dev/null +++ b/host/lib/usrp/usrp2/clock_ctrl.hpp @@ -0,0 +1,109 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_CLOCK_CTRL_HPP +#define INCLUDED_CLOCK_CTRL_HPP + +#include "usrp2_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +class usrp2_clock_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp2_clock_ctrl> sptr; + + /*! + * Make a clock config for the ad9510 ic. + * \param _iface a pointer to the usrp2 interface object + * \return a new clock control object + */ + static sptr make(usrp2_iface::sptr iface); + + /*! + * Get the master clock frequency for the fpga. + * \return the clock frequency in Hz + */ + virtual double get_master_clock_rate(void) = 0; + + /*! + * Enable/disable the rx dboard clock. + * \param enb true to enable + */ + virtual void enable_rx_dboard_clock(bool enb) = 0; + + /*! + * Set the clock rate on the rx dboard clock. + * \param rate the new clock rate + * \throw exception when rate invalid + */ + virtual void set_rate_rx_dboard_clock(double rate) = 0; + + /*! + * Get a list of possible rx dboard clock rates. + * \return a list of clock rates in Hz + */ + virtual std::vector<double> get_rates_rx_dboard_clock(void) = 0; + + /*! + * Enable/disable the tx dboard clock. + * \param enb true to enable + */ + virtual void enable_tx_dboard_clock(bool enb) = 0; + + /*! + * Set the clock rate on the tx dboard clock. + * \param rate the new clock rate + * \throw exception when rate invalid + */ + virtual void set_rate_tx_dboard_clock(double rate) = 0; + + /*! + * Get a list of possible tx dboard clock rates. + * \return a list of clock rates in Hz + */ + virtual std::vector<double> get_rates_tx_dboard_clock(void) = 0; + + /*! + * Enable/disable external reference. + * \param enb true to enable + */ + virtual void enable_external_ref(bool enb) = 0; + + /*! + * Enable/disable test clock output. + * \param enb true to enable + */ + virtual void enable_test_clock(bool enb) = 0; + + /*! + * Enable/disable the ref clock output over the serdes cable. + * \param enb true to enable + */ + virtual void enable_mimo_clock_out(bool enb) = 0; + + /*! + * Set the output delay of the mimo clock + * Used to synchronise daisy-chained USRPs over the MIMO cable + * Can also be used to adjust delay for uneven reference cable lengths + * \param delay the clock delay in seconds + */ + virtual void set_mimo_clock_delay(double delay) = 0; + +}; + +#endif /* INCLUDED_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/codec_ctrl.cpp b/host/lib/usrp/usrp2/codec_ctrl.cpp new file mode 100644 index 000000000..06bf83b15 --- /dev/null +++ b/host/lib/usrp/usrp2/codec_ctrl.cpp @@ -0,0 +1,216 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9777_regs.hpp" +#include "ads62p44_regs.hpp" +#include "usrp2_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/exception.hpp> +#include <boost/cstdint.hpp> +#include <boost/foreach.hpp> + +using namespace uhd; + +/*! + * A usrp2 codec control specific to the ad9777 ic. + */ +class usrp2_codec_ctrl_impl : public usrp2_codec_ctrl{ +public: + usrp2_codec_ctrl_impl(usrp2_iface::sptr iface){ + _iface = iface; + + //setup the ad9777 dac + _ad9777_regs.x_1r_2r_mode = ad9777_regs_t::X_1R_2R_MODE_1R; + _ad9777_regs.filter_interp_rate = ad9777_regs_t::FILTER_INTERP_RATE_4X; + _ad9777_regs.mix_mode = ad9777_regs_t::MIX_MODE_COMPLEX; + _ad9777_regs.pll_divide_ratio = ad9777_regs_t::PLL_DIVIDE_RATIO_DIV1; + _ad9777_regs.pll_state = ad9777_regs_t::PLL_STATE_ON; + _ad9777_regs.auto_cp_control = ad9777_regs_t::AUTO_CP_CONTROL_AUTO; + //I dac values + _ad9777_regs.idac_fine_gain_adjust = 0; + _ad9777_regs.idac_coarse_gain_adjust = 0xf; + _ad9777_regs.idac_offset_adjust_lsb = 0; + _ad9777_regs.idac_offset_adjust_msb = 0; + //Q dac values + _ad9777_regs.qdac_fine_gain_adjust = 0; + _ad9777_regs.qdac_coarse_gain_adjust = 0xf; + _ad9777_regs.qdac_offset_adjust_lsb = 0; + _ad9777_regs.qdac_offset_adjust_msb = 0; + //write all regs + for(boost::uint8_t addr = 0; addr <= 0xC; addr++){ + this->send_ad9777_reg(addr); + } + set_tx_mod_mode(0); + + //power-up adc + switch(_iface->get_rev()){ + case usrp2_iface::USRP2_REV3: + case usrp2_iface::USRP2_REV4: + _iface->poke32(U2_REG_MISC_CTRL_ADC, U2_FLAG_MISC_CTRL_ADC_ON); + break; + + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + _ads62p44_regs.reset = 1; + this->send_ads62p44_reg(0x00); //issue a reset to the ADC + //everything else should be pretty much default, i think + //_ads62p44_regs.decimation = DECIMATION_DECIMATE_1; + _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_NORMAL; + this->send_ads62p44_reg(0x14); + this->set_rx_analog_gain(1); + break; + + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + _ads62p44_regs.reset = 1; + this->send_ads62p44_reg(0x00); //issue a reset to the ADC + //everything else should be pretty much default, i think + //_ads62p44_regs.decimation = DECIMATION_DECIMATE_1; + _ads62p44_regs.override = 1; + this->send_ads62p44_reg(0x14); + _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_NORMAL; + _ads62p44_regs.output_interface = ads62p44_regs_t::OUTPUT_INTERFACE_LVDS; + _ads62p44_regs.lvds_current = ads62p44_regs_t::LVDS_CURRENT_2_5MA; + _ads62p44_regs.lvds_data_term = ads62p44_regs_t::LVDS_DATA_TERM_100; + this->send_ads62p44_reg(0x11); + this->send_ads62p44_reg(0x12); + this->send_ads62p44_reg(0x14); + this->set_rx_analog_gain(1); + break; + + case usrp2_iface::USRP_NXXX: break; + } + } + + ~usrp2_codec_ctrl_impl(void){UHD_SAFE_CALL( + //power-down dac + _ad9777_regs.power_down_mode = 1; + this->send_ad9777_reg(0); + + //power-down adc + switch(_iface->get_rev()){ + case usrp2_iface::USRP2_REV3: + case usrp2_iface::USRP2_REV4: + _iface->poke32(U2_REG_MISC_CTRL_ADC, U2_FLAG_MISC_CTRL_ADC_OFF); + break; + + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + //send a global power-down to the ADC here... it will get lifted on reset + _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_GLOBAL_PD; + this->send_ads62p44_reg(0x14); + break; + + case usrp2_iface::USRP_NXXX: break; + } + )} + + void set_tx_mod_mode(int mod_mode){ + //set the sign of the frequency shift + _ad9777_regs.modulation_form = (mod_mode > 0)? + ad9777_regs_t::MODULATION_FORM_E_PLUS_JWT: + ad9777_regs_t::MODULATION_FORM_E_MINUS_JWT + ; + + //set the frequency shift + switch(std::abs(mod_mode)){ + case 0: + case 1: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_NONE; break; + case 2: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_2; break; + case 4: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_4; break; + case 8: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_8; break; + default: throw uhd::value_error("unknown modulation mode for ad9777"); + } + + this->send_ad9777_reg(0x01); //set the register + } + + void set_rx_digital_gain(double gain) { //fine digital gain + switch(_iface->get_rev()){ + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + _ads62p44_regs.fine_gain = int(gain/0.5); + this->send_ads62p44_reg(0x17); + break; + + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + void set_rx_digital_fine_gain(double gain) { //gain correction + switch(_iface->get_rev()){ + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + _ads62p44_regs.gain_correction = int(gain / 0.05); + this->send_ads62p44_reg(0x1A); + break; + + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + void set_rx_analog_gain(bool /*gain*/) { //turns on/off analog 3.5dB preamp + switch(_iface->get_rev()){ + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + _ads62p44_regs.coarse_gain = ads62p44_regs_t::COARSE_GAIN_3_5DB;//gain ? ads62p44_regs_t::COARSE_GAIN_3_5DB : ads62p44_regs_t::COARSE_GAIN_0DB; + this->send_ads62p44_reg(0x14); + break; + + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + +private: + ad9777_regs_t _ad9777_regs; + ads62p44_regs_t _ads62p44_regs; + usrp2_iface::sptr _iface; + + void send_ad9777_reg(boost::uint8_t addr){ + boost::uint16_t reg = _ad9777_regs.get_write_reg(addr); + UHD_LOGV(always) << "send_ad9777_reg: " << std::hex << reg << std::endl; + _iface->write_spi( + SPI_SS_AD9777, spi_config_t::EDGE_RISE, + reg, 16 + ); + } + + void send_ads62p44_reg(boost::uint8_t addr) { + boost::uint16_t reg = _ads62p44_regs.get_write_reg(addr); + _iface->write_spi( + SPI_SS_ADS62P44, spi_config_t::EDGE_FALL, + reg, 16 + ); + } +}; + +/*********************************************************************** + * Public make function for the usrp2 codec control + **********************************************************************/ +usrp2_codec_ctrl::sptr usrp2_codec_ctrl::make(usrp2_iface::sptr iface){ + return sptr(new usrp2_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp2/codec_ctrl.hpp b/host/lib/usrp/usrp2/codec_ctrl.hpp new file mode 100644 index 000000000..ca300e2b1 --- /dev/null +++ b/host/lib/usrp/usrp2/codec_ctrl.hpp @@ -0,0 +1,68 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_CODEC_CTRL_HPP +#define INCLUDED_CODEC_CTRL_HPP + +#include "usrp2_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class usrp2_codec_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp2_codec_ctrl> sptr; + + /*! + * Make a codec control for the DAC and ADC. + * \param _iface a pointer to the usrp2 interface object + * \return a new codec control object + */ + static sptr make(usrp2_iface::sptr iface); + + /*! + * Set the modulation mode for the DAC. + * Possible modes are 0, +/-1, +/-2, +/-4, +/-8 + * which correspond to shifts of fs/mod_mode. + * A mode of 0 or +/-1 means no modulation. + * \param mod_mode the modulation mode + */ + virtual void set_tx_mod_mode(int mod_mode) = 0; + + /*! + * Set the analog preamplifier on the USRP2+ ADC (ADS62P44). + * \param gain enable or disable the 3.5dB preamp + */ + + virtual void set_rx_analog_gain(bool gain) = 0; + + /*! + * Set the digital gain on the USRP2+ ADC (ADS62P44). + * \param gain from 0-6dB + */ + + virtual void set_rx_digital_gain(double gain) = 0; + + /*! + * Set the digital gain correction on the USRP2+ ADC (ADS62P44). + * \param gain from 0-0.5dB + */ + + virtual void set_rx_digital_fine_gain(double gain) = 0; + +}; + +#endif /* INCLUDED_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp new file mode 100644 index 000000000..bc510c8a1 --- /dev/null +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -0,0 +1,295 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "gpio_core_200.hpp" +#include "usrp2_iface.hpp" +#include "clock_ctrl.hpp" +#include "usrp2_regs.hpp" //wishbone address constants +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio.hpp> //htonl and ntohl +#include <boost/math/special_functions/round.hpp> +#include "ad7922_regs.hpp" //aux adc +#include "ad5623_regs.hpp" //aux dac + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class usrp2_dboard_iface : public dboard_iface{ +public: + usrp2_dboard_iface(usrp2_iface::sptr iface, usrp2_clock_ctrl::sptr clock_ctrl); + ~usrp2_dboard_iface(void); + + special_props_t get_special_props(void){ + special_props_t props; + props.soft_clock_divider = false; + props.mangle_i2c_addrs = false; + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, double); + double read_aux_adc(unit_t, aux_adc_t); + + void _set_pin_ctrl(unit_t, boost::uint16_t); + void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); + void _set_gpio_ddr(unit_t, boost::uint16_t); + void _set_gpio_out(unit_t, boost::uint16_t); + void set_gpio_debug(unit_t, int); + boost::uint16_t read_gpio(unit_t); + + void write_i2c(boost::uint8_t, const byte_vector_t &); + byte_vector_t read_i2c(boost::uint8_t, size_t); + + void set_clock_rate(unit_t, double); + double get_clock_rate(unit_t); + std::vector<double> get_clock_rates(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + + void write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + boost::uint32_t read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + +private: + usrp2_iface::sptr _iface; + usrp2_clock_ctrl::sptr _clock_ctrl; + gpio_core_200::sptr _gpio; + + uhd::dict<unit_t, ad5623_regs_t> _dac_regs; + uhd::dict<unit_t, double> _clock_rates; + void _write_aux_dac(unit_t); +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_usrp2_dboard_iface( + usrp2_iface::sptr iface, + usrp2_clock_ctrl::sptr clock_ctrl +){ + return dboard_iface::sptr(new usrp2_dboard_iface(iface, clock_ctrl)); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp2_dboard_iface::usrp2_dboard_iface( + usrp2_iface::sptr iface, + usrp2_clock_ctrl::sptr clock_ctrl +){ + _iface = iface; + _clock_ctrl = clock_ctrl; + _gpio = gpio_core_200::make(_iface, U2_REG_SR_ADDR(SR_GPIO), U2_REG_GPIO_RB); + + //reset the aux dacs + _dac_regs[UNIT_RX] = ad5623_regs_t(); + _dac_regs[UNIT_TX] = ad5623_regs_t(); + BOOST_FOREACH(unit_t unit, _dac_regs.keys()){ + _dac_regs[unit].data = 1; + _dac_regs[unit].addr = ad5623_regs_t::ADDR_ALL; + _dac_regs[unit].cmd = ad5623_regs_t::CMD_RESET; + this->_write_aux_dac(unit); + } + + //init the clock rate shadows with max rate clock + this->set_clock_rate(UNIT_RX, sorted(this->get_clock_rates(UNIT_RX)).back()); + this->set_clock_rate(UNIT_TX, sorted(this->get_clock_rates(UNIT_TX)).back()); +} + +usrp2_dboard_iface::~usrp2_dboard_iface(void){ + /* NOP */ +} + +/*********************************************************************** + * Clocks + **********************************************************************/ +void usrp2_dboard_iface::set_clock_rate(unit_t unit, double rate){ + _clock_rates[unit] = rate; //set to shadow + switch(unit){ + case UNIT_RX: _clock_ctrl->set_rate_rx_dboard_clock(rate); return; + case UNIT_TX: _clock_ctrl->set_rate_tx_dboard_clock(rate); return; + } +} + +double usrp2_dboard_iface::get_clock_rate(unit_t unit){ + return _clock_rates[unit]; //get from shadow +} + +std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ + switch(unit){ + case UNIT_RX: return _clock_ctrl->get_rates_rx_dboard_clock(); + case UNIT_TX: return _clock_ctrl->get_rates_tx_dboard_clock(); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ + switch(unit){ + case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; + case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; + } +} + +double usrp2_dboard_iface::get_codec_rate(unit_t){ + return _clock_ctrl->get_master_clock_rate(); +} +/*********************************************************************** + * GPIO + **********************************************************************/ +void usrp2_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ + return _gpio->set_pin_ctrl(unit, value); +} + +void usrp2_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ + return _gpio->set_gpio_ddr(unit, value); +} + +void usrp2_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ + return _gpio->set_gpio_out(unit, value); +} + +boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); +} + +void usrp2_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ + return _gpio->set_atr_reg(unit, atr, value); +} + +void usrp2_dboard_iface::set_gpio_debug(unit_t, int){ + throw uhd::not_implemented_error("no set_gpio_debug implemented"); +} + +/*********************************************************************** + * SPI + **********************************************************************/ +static const uhd::dict<dboard_iface::unit_t, int> unit_to_spi_dev = map_list_of + (dboard_iface::UNIT_TX, SPI_SS_TX_DB) + (dboard_iface::UNIT_RX, SPI_SS_RX_DB) +; + +void usrp2_dboard_iface::write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + _iface->write_spi(unit_to_spi_dev[unit], config, data, num_bits); +} + +boost::uint32_t usrp2_dboard_iface::read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + return _iface->read_spi(unit_to_spi_dev[unit], config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp2_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ + return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp2_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ + return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ + static const uhd::dict<unit_t, int> unit_to_spi_dac = map_list_of + (UNIT_RX, SPI_SS_RX_DAC) + (UNIT_TX, SPI_SS_TX_DAC) + ; + _iface->write_spi( + unit_to_spi_dac[unit], spi_config_t::EDGE_FALL, + _dac_regs[unit].get_reg(), 24 + ); +} + +void usrp2_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value){ + _dac_regs[unit].data = boost::math::iround(4095*value/3.3); + _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; + + typedef uhd::dict<aux_dac_t, ad5623_regs_t::addr_t> aux_dac_to_addr; + static const uhd::dict<unit_t, aux_dac_to_addr> unit_to_which_to_addr = map_list_of + (UNIT_RX, map_list_of + (AUX_DAC_A, ad5623_regs_t::ADDR_DAC_B) + (AUX_DAC_B, ad5623_regs_t::ADDR_DAC_A) + (AUX_DAC_C, ad5623_regs_t::ADDR_DAC_A) + (AUX_DAC_D, ad5623_regs_t::ADDR_DAC_B) + ) + (UNIT_TX, map_list_of + (AUX_DAC_A, ad5623_regs_t::ADDR_DAC_A) + (AUX_DAC_B, ad5623_regs_t::ADDR_DAC_B) + (AUX_DAC_C, ad5623_regs_t::ADDR_DAC_B) + (AUX_DAC_D, ad5623_regs_t::ADDR_DAC_A) + ) + ; + _dac_regs[unit].addr = unit_to_which_to_addr[unit][which]; + this->_write_aux_dac(unit); +} + +double usrp2_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which){ + static const uhd::dict<unit_t, int> unit_to_spi_adc = map_list_of + (UNIT_RX, SPI_SS_RX_ADC) + (UNIT_TX, SPI_SS_TX_ADC) + ; + + //setup spi config args + spi_config_t config; + config.mosi_edge = spi_config_t::EDGE_FALL; + config.miso_edge = spi_config_t::EDGE_RISE; + + //setup the spi registers + ad7922_regs_t ad7922_regs; + switch(which){ + case AUX_ADC_A: ad7922_regs.mod = 0; break; + case AUX_ADC_B: ad7922_regs.mod = 1; break; + } ad7922_regs.chn = ad7922_regs.mod; //normal mode: mod == chn + + //write and read spi + _iface->write_spi( + unit_to_spi_adc[unit], config, + ad7922_regs.get_reg(), 16 + ); + ad7922_regs.set_reg(boost::uint16_t(_iface->read_spi( + unit_to_spi_adc[unit], config, + ad7922_regs.get_reg(), 16 + ))); + + //convert to voltage and return + return 3.3*ad7922_regs.result/4095; +} diff --git a/host/lib/usrp/usrp2/fw_common.h b/host/lib/usrp/usrp2/fw_common.h new file mode 100644 index 000000000..0babf7445 --- /dev/null +++ b/host/lib/usrp/usrp2/fw_common.h @@ -0,0 +1,151 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_FW_COMMON_H +#define INCLUDED_USRP2_FW_COMMON_H + +#include <stdint.h> + +/*! + * Structs and constants for usrp2 communication. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +//fpga and firmware compatibility numbers +#define USRP2_FPGA_COMPAT_NUM 9 +#define USRP2_FW_COMPAT_NUM 11 +#define USRP2_FW_VER_MINOR 2 + +//used to differentiate control packets over data port +#define USRP2_INVALID_VRT_HEADER 0 + +// udp ports for the usrp2 communication +// Dynamic and/or private ports: 49152-65535 +#define USRP2_UDP_CTRL_PORT 49152 +//#define USRP2_UDP_UPDATE_PORT 49154 +#define USRP2_UDP_RX_DSP0_PORT 49156 +#define USRP2_UDP_TX_DSP0_PORT 49157 +#define USRP2_UDP_RX_DSP1_PORT 49158 +#define USRP2_UDP_UART_BASE_PORT 49170 +#define USRP2_UDP_UART_GPS_PORT 49172 + +// Map for virtual firmware regs (not very big so we can keep it here for now) +#define U2_FW_REG_LOCK_TIME 0 +#define U2_FW_REG_LOCK_GPID 1 +#define U2_FW_REG_VER_MINOR 7 + +//////////////////////////////////////////////////////////////////////// +// I2C addresses +//////////////////////////////////////////////////////////////////////// +#define USRP2_I2C_DEV_EEPROM 0x50 // 24LC02[45]: 7-bits 1010xxx +#define USRP2_I2C_ADDR_MBOARD (USRP2_I2C_DEV_EEPROM | 0x0) +#define USRP2_I2C_ADDR_TX_DB (USRP2_I2C_DEV_EEPROM | 0x4) +#define USRP2_I2C_ADDR_RX_DB (USRP2_I2C_DEV_EEPROM | 0x5) + +//////////////////////////////////////////////////////////////////////// +// EEPROM Layout +//////////////////////////////////////////////////////////////////////// +#define USRP2_EE_MBOARD_REV 0x00 //2 bytes, little-endian (historic, don't blame me) +#define USRP2_EE_MBOARD_MAC_ADDR 0x02 //6 bytes +#define USRP2_EE_MBOARD_IP_ADDR 0x0C //uint32, big-endian +#define USRP2_EE_MBOARD_BOOTLOADER_FLAGS 0xF7 + +typedef enum{ + USRP2_CTRL_ID_HUH_WHAT = ' ', + //USRP2_CTRL_ID_FOR_SURE, //TODO error condition enums + //USRP2_CTRL_ID_SUX_MAN, + + USRP2_CTRL_ID_WAZZUP_BRO = 'a', + USRP2_CTRL_ID_WAZZUP_DUDE = 'A', + + USRP2_CTRL_ID_TRANSACT_ME_SOME_SPI_BRO = 's', + USRP2_CTRL_ID_OMG_TRANSACTED_SPI_DUDE = 'S', + + USRP2_CTRL_ID_DO_AN_I2C_READ_FOR_ME_BRO = 'i', + USRP2_CTRL_ID_HERES_THE_I2C_DATA_DUDE = 'I', + + USRP2_CTRL_ID_WRITE_THESE_I2C_VALUES_BRO = 'h', + USRP2_CTRL_ID_COOL_IM_DONE_I2C_WRITE_DUDE = 'H', + + USRP2_CTRL_ID_GET_THIS_REGISTER_FOR_ME_BRO = 'r', + USRP2_CTRL_ID_OMG_GOT_REGISTER_SO_BAD_DUDE = 'R', + + USRP2_CTRL_ID_HOLLER_AT_ME_BRO = 'l', + USRP2_CTRL_ID_HOLLER_BACK_DUDE = 'L', + + USRP2_CTRL_ID_PEACE_OUT = '~' + +} usrp2_ctrl_id_t; + +typedef enum{ + USRP2_DIR_RX = 'r', + USRP2_DIR_TX = 't' +} usrp2_dir_which_t; + +typedef enum{ + USRP2_CLK_EDGE_RISE = 'r', + USRP2_CLK_EDGE_FALL = 'f' +} usrp2_clk_edge_t; + +typedef enum{ + USRP2_REG_ACTION_FPGA_PEEK32 = 1, + USRP2_REG_ACTION_FPGA_PEEK16 = 2, + USRP2_REG_ACTION_FPGA_POKE32 = 3, + USRP2_REG_ACTION_FPGA_POKE16 = 4, + USRP2_REG_ACTION_FW_PEEK32 = 5, + USRP2_REG_ACTION_FW_POKE32 = 6 +} usrp2_reg_action_t; + +typedef struct{ + uint32_t proto_ver; + uint32_t id; + uint32_t seq; + union{ + uint32_t ip_addr; + struct { + uint32_t dev; + uint32_t data; + uint8_t miso_edge; + uint8_t mosi_edge; + uint8_t num_bits; + uint8_t readback; + } spi_args; + struct { + uint8_t addr; + uint8_t bytes; + uint8_t data[20]; + } i2c_args; + struct { + uint32_t addr; + uint32_t data; + uint8_t action; + } reg_args; + struct { + uint32_t len; + } echo_args; + } data; +} usrp2_ctrl_data_t; + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_USRP2_FW_COMMON_H */ diff --git a/host/lib/usrp/usrp2/io_impl.cpp b/host/lib/usrp/usrp2/io_impl.cpp new file mode 100644 index 000000000..d32ffb62c --- /dev/null +++ b/host/lib/usrp/usrp2/io_impl.cpp @@ -0,0 +1,492 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "validate_subdev_spec.hpp" +#include "async_packet_handler.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "usrp2_impl.hpp" +#include "usrp2_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/make_shared.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; +namespace pt = boost::posix_time; + +/*********************************************************************** + * helpers + **********************************************************************/ +static UHD_INLINE pt::time_duration to_time_dur(double timeout){ + return pt::microseconds(long(timeout*1e6)); +} + +static UHD_INLINE double from_time_dur(const pt::time_duration &time_dur){ + return 1e-6*time_dur.total_microseconds(); +} + +/*********************************************************************** + * constants + **********************************************************************/ +static const size_t vrt_send_header_offset_words32 = 1; + +/*********************************************************************** + * flow control monitor for a single tx channel + * - the pirate thread calls update + * - the get send buffer calls check + **********************************************************************/ +class flow_control_monitor{ +public: + typedef boost::uint32_t seq_type; + typedef boost::shared_ptr<flow_control_monitor> sptr; + + /*! + * Make a new flow control monitor. + * \param max_seqs_out num seqs before throttling + */ + flow_control_monitor(seq_type max_seqs_out):_max_seqs_out(max_seqs_out){ + this->clear(); + _ready_fcn = boost::bind(&flow_control_monitor::ready, this); + } + + //! Clear the monitor, Ex: when a streamer is created + void clear(void){ + _last_seq_out = 0; + _last_seq_ack = 0; + } + + /*! + * Gets the current sequence number to go out. + * Increments the sequence for the next call + * \return the sequence to be sent to the dsp + */ + UHD_INLINE seq_type get_curr_seq_out(void){ + return _last_seq_out++; + } + + /*! + * Check the flow control condition. + * \param timeout the timeout in seconds + * \return false on timeout + */ + UHD_INLINE bool check_fc_condition(double timeout){ + boost::mutex::scoped_lock lock(_fc_mutex); + if (this->ready()) return true; + boost::this_thread::disable_interruption di; //disable because the wait can throw + return _fc_cond.timed_wait(lock, to_time_dur(timeout), _ready_fcn); + } + + /*! + * Update the flow control condition. + * \param seq the last sequence number to be ACK'd + */ + UHD_INLINE void update_fc_condition(seq_type seq){ + boost::mutex::scoped_lock lock(_fc_mutex); + _last_seq_ack = seq; + lock.unlock(); + _fc_cond.notify_one(); + } + +private: + bool ready(void){ + return seq_type(_last_seq_out -_last_seq_ack) < _max_seqs_out; + } + + boost::mutex _fc_mutex; + boost::condition _fc_cond; + seq_type _last_seq_out, _last_seq_ack; + const seq_type _max_seqs_out; + boost::function<bool(void)> _ready_fcn; +}; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew + * - alignment buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct usrp2_impl::io_impl{ + + io_impl(void): + async_msg_fifo(1000/*messages deep*/), + tick_rate(1 /*non-zero default*/) + { + /* NOP */ + } + + ~io_impl(void){ + //Manually deconstuct the tasks, since this was not happening automatically. + pirate_tasks.clear(); + } + + managed_send_buffer::sptr get_send_buff(size_t chan, double timeout){ + flow_control_monitor &fc_mon = *fc_mons[chan]; + + //wait on flow control w/ timeout + if (not fc_mon.check_fc_condition(timeout)) return managed_send_buffer::sptr(); + + //get a buffer from the transport w/ timeout + managed_send_buffer::sptr buff = tx_xports[chan]->get_send_buff(timeout); + + //write the flow control word into the buffer + if (buff.get()) buff->cast<boost::uint32_t *>()[0] = uhd::htonx(fc_mon.get_curr_seq_out()); + + return buff; + } + + //tx dsp: xports and flow control monitors + std::vector<zero_copy_if::sptr> tx_xports; + std::vector<flow_control_monitor::sptr> fc_mons; + + //methods and variables for the pirate crew + void recv_pirate_loop(zero_copy_if::sptr, size_t); + std::list<task::sptr> pirate_tasks; + bounded_buffer<async_metadata_t> async_msg_fifo; + double tick_rate; +}; + +/*********************************************************************** + * Receive Pirate Loop + * - while raiding, loot for message packet + * - update flow control condition count + * - put async message packets into queue + **********************************************************************/ +void usrp2_impl::io_impl::recv_pirate_loop( + zero_copy_if::sptr err_xport, size_t index +){ + set_thread_priority_safe(); + + //store a reference to the flow control monitor (offset by max dsps) + flow_control_monitor &fc_mon = *(this->fc_mons[index]); + + while (not boost::this_thread::interruption_requested()){ + managed_recv_buffer::sptr buff = err_xport->get_recv_buff(); + if (not buff.get()) continue; //ignore timeout/error buffers + + try{ + //extract the vrt header packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *vrt_hdr = buff->cast<const boost::uint32_t *>(); + vrt::if_hdr_unpack_be(vrt_hdr, if_packet_info); + + //handle a tx async report message + if (if_packet_info.sid == USRP2_TX_ASYNC_SID and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff(uhd::ntohx<boost::uint32_t>, metadata, if_packet_info, vrt_hdr, tick_rate, index); + + //catch the flow control packets and react + if (metadata.event_code == 0){ + boost::uint32_t fc_word32 = (vrt_hdr + if_packet_info.num_header_words32)[1]; + fc_mon.update_fc_condition(uhd::ntohx(fc_word32)); + continue; + } + //else UHD_MSG(often) << "metadata.event_code " << metadata.event_code << std::endl; + async_msg_fifo.push_with_pop_on_full(metadata); + + standard_async_msg_prints(metadata); + } + else{ + //TODO unknown received packet, may want to print error... + } + }catch(const std::exception &e){ + UHD_MSG(error) << "Error in recv pirate loop: " << e.what() << std::endl; + } + } +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void usrp2_impl::io_init(void){ + //create new io impl + _io_impl = UHD_PIMPL_MAKE(io_impl, ()); + + //init first so we dont have an access race + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + //init the tx xport and flow control monitor + _io_impl->tx_xports.push_back(_mbc[mb].tx_dsp_xport); + _io_impl->fc_mons.push_back(flow_control_monitor::sptr(new flow_control_monitor( + USRP2_SRAM_BYTES/_mbc[mb].tx_dsp_xport->get_send_frame_size() + ))); + } + + //allocate streamer weak ptrs containers + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + _mbc[mb].rx_streamers.resize(_mbc[mb].rx_dsps.size()); + _mbc[mb].tx_streamers.resize(1/*known to be 1 dsp*/); + } + + //create a new pirate thread for each zc if (yarr!!) + size_t index = 0; + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + //spawn a new pirate to plunder the recv booty + _io_impl->pirate_tasks.push_back(task::make(boost::bind( + &usrp2_impl::io_impl::recv_pirate_loop, _io_impl.get(), + _mbc[mb].tx_dsp_xport, index++ + ))); + } +} + +void usrp2_impl::update_tick_rate(const double rate){ + _io_impl->tick_rate = rate; //shadow for async msg + + //update the tick rate on all existing streamers -> thread safe + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + for (size_t i = 0; i < _mbc[mb].rx_streamers.size(); i++){ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_mbc[mb].rx_streamers[i].lock()); + if (my_streamer.get() == NULL) continue; + my_streamer->set_tick_rate(rate); + } + for (size_t i = 0; i < _mbc[mb].tx_streamers.size(); i++){ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_mbc[mb].tx_streamers[i].lock()); + if (my_streamer.get() == NULL) continue; + my_streamer->set_tick_rate(rate); + } + } +} + +void usrp2_impl::update_rx_samp_rate(const std::string &mb, const size_t dsp, const double rate){ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_mbc[mb].rx_streamers[dsp].lock()); + if (my_streamer.get() == NULL) return; + + my_streamer->set_samp_rate(rate); + const double adj = _mbc[mb].rx_dsps[dsp]->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void usrp2_impl::update_tx_samp_rate(const std::string &mb, const size_t dsp, const double rate){ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_mbc[mb].tx_streamers[dsp].lock()); + if (my_streamer.get() == NULL) return; + + my_streamer->set_samp_rate(rate); + const double adj = _mbc[mb].tx_dsp->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void usrp2_impl::update_rates(void){ + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + fs_path root = "/mboards/" + mb; + _tree->access<double>(root / "tick_rate").update(); + + //and now that the tick rate is set, init the host rates to something + BOOST_FOREACH(const std::string &name, _tree->list(root / "rx_dsps")){ + _tree->access<double>(root / "rx_dsps" / name / "rate" / "value").update(); + } + BOOST_FOREACH(const std::string &name, _tree->list(root / "tx_dsps")){ + _tree->access<double>(root / "tx_dsps" / name / "rate" / "value").update(); + } + } +} + +void usrp2_impl::update_rx_subdev_spec(const std::string &which_mb, const subdev_spec_t &spec){ + fs_path root = "/mboards/" + which_mb + "/dboards"; + + //sanity checking + validate_subdev_spec(_tree, spec, "rx", which_mb); + + //setup mux for this spec + bool fe_swapped = false; + for (size_t i = 0; i < spec.size(); i++){ + const std::string conn = _tree->access<std::string>(root / spec[i].db_name / "rx_frontends" / spec[i].sd_name / "connection").get(); + if (i == 0 and (conn == "QI" or conn == "Q")) fe_swapped = true; + _mbc[which_mb].rx_dsps[i]->set_mux(conn, fe_swapped); + } + _mbc[which_mb].rx_fe->set_mux(fe_swapped); + + //compute the new occupancy and resize + _mbc[which_mb].rx_chan_occ = spec.size(); + size_t nchan = 0; + BOOST_FOREACH(const std::string &mb, _mbc.keys()) nchan += _mbc[mb].rx_chan_occ; +} + +void usrp2_impl::update_tx_subdev_spec(const std::string &which_mb, const subdev_spec_t &spec){ + fs_path root = "/mboards/" + which_mb + "/dboards"; + + //sanity checking + validate_subdev_spec(_tree, spec, "tx", which_mb); + + //set the mux for this spec + const std::string conn = _tree->access<std::string>(root / spec[0].db_name / "tx_frontends" / spec[0].sd_name / "connection").get(); + _mbc[which_mb].tx_fe->set_mux(conn); + + //compute the new occupancy and resize + _mbc[which_mb].tx_chan_occ = spec.size(); + size_t nchan = 0; + BOOST_FOREACH(const std::string &mb, _mbc.keys()) nchan += _mbc[mb].tx_chan_occ; +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool usrp2_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +){ + boost::this_thread::disable_interruption di; //disable because the wait can throw + return _io_impl->async_msg_fifo.pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr usrp2_impl::get_rx_stream(const uhd::stream_args_t &args_){ + stream_args_t args = args_; + + //setup defaults for unspecified values + args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = _mbc[_mbc.keys().front()].rx_dsp_xports[0]->get_recv_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + + //init some streamer stuff + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_unpacker(&vrt::if_hdr_unpack_be); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_be"; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //bind callbacks for the handler + for (size_t chan_i = 0; chan_i < args.channels.size(); chan_i++){ + const size_t chan = args.channels[chan_i]; + size_t num_chan_so_far = 0; + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + num_chan_so_far += _mbc[mb].rx_chan_occ; + if (chan < num_chan_so_far){ + const size_t dsp = chan + _mbc[mb].rx_chan_occ - num_chan_so_far; + _mbc[mb].rx_dsps[dsp]->set_nsamps_per_packet(spp); //seems to be a good place to set this + _mbc[mb].rx_dsps[dsp]->setup(args); + my_streamer->set_xport_chan_get_buff(chan_i, boost::bind( + &zero_copy_if::get_recv_buff, _mbc[mb].rx_dsp_xports[dsp], _1 + ), true /*flush*/); + _mbc[mb].rx_streamers[dsp] = my_streamer; //store weak pointer + break; + } + } + } + + //set the packet threshold to be an entire socket buffer's worth + const size_t packets_per_sock_buff = size_t(50e6/_mbc[_mbc.keys().front()].rx_dsp_xports[0]->get_recv_frame_size()); + my_streamer->set_alignment_failure_threshold(packets_per_sock_buff); + + //sets all tick and samp rates on this streamer + this->update_rates(); + + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr usrp2_impl::get_tx_stream(const uhd::stream_args_t &args_){ + stream_args_t args = args_; + + //setup defaults for unspecified values + args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //calculate packet size + static const size_t hdr_size = 0 + + vrt_send_header_offset_words32*sizeof(boost::uint32_t) + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().sid) //no stream id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = _mbc[_mbc.keys().front()].tx_dsp_xport->get_send_frame_size() - hdr_size; + const size_t spp = bpp/convert::get_bytes_per_item(args.otw_format); + + //make the new streamer given the samples per packet + boost::shared_ptr<sph::send_packet_streamer> my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + + //init some streamer stuff + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_packer(&vrt::if_hdr_pack_be, vrt_send_header_offset_words32); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_be"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //bind callbacks for the handler + for (size_t chan_i = 0; chan_i < args.channels.size(); chan_i++){ + const size_t chan = args.channels[chan_i]; + size_t num_chan_so_far = 0; + size_t abs = 0; + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + num_chan_so_far += _mbc[mb].tx_chan_occ; + if (chan < num_chan_so_far){ + const size_t dsp = chan + _mbc[mb].tx_chan_occ - num_chan_so_far; + if (not args.args.has_key("noclear")){ + _io_impl->fc_mons[abs]->clear(); + } + _mbc[mb].tx_dsp->setup(args); + my_streamer->set_xport_chan_get_buff(chan_i, boost::bind( + &usrp2_impl::io_impl::get_send_buff, _io_impl.get(), abs, _1 + )); + _mbc[mb].tx_streamers[dsp] = my_streamer; //store weak pointer + break; + } + abs += 1; //assume 1 tx dsp + } + } + + //sets all tick and samp rates on this streamer + this->update_rates(); + + return my_streamer; +} diff --git a/host/lib/usrp/usrp2/usrp2_clk_regs.hpp b/host/lib/usrp/usrp2/usrp2_clk_regs.hpp new file mode 100644 index 000000000..8b185eac0 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_clk_regs.hpp @@ -0,0 +1,87 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_CLK_REGS_HPP +#define INCLUDED_USRP2_CLK_REGS_HPP + +#include "usrp2_iface.hpp" + +class usrp2_clk_regs_t { +public: + usrp2_clk_regs_t(void) { ; } + usrp2_clk_regs_t(usrp2_iface::rev_type rev) { + test = 0; + fpga = 1; + dac = 3; + + switch(rev) { + case usrp2_iface::USRP2_REV3: + exp = 2; + adc = 4; + serdes = 2; + tx_db = 6; + break; + case usrp2_iface::USRP2_REV4: + exp = 5; + adc = 4; + serdes = 2; + tx_db = 6; + break; + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + exp = 6; + adc = 2; + serdes = 4; + tx_db = 5; + break; + case usrp2_iface::USRP_NXXX: + //dont throw, it may be unitialized + break; + } + + rx_db = 7; + } + + static int output(int clknum) { return 0x3C + clknum; } + static int div_lo(int clknum) { return 0x48 + 2 * clknum; } + static int div_hi(int clknum) { return 0x49 + 2 * clknum; } + + const static int acounter = 0x04; + const static int bcounter_msb = 0x05; + const static int bcounter_lsb = 0x06; + const static int pll_1 = 0x07; + const static int pll_2 = 0x08; + const static int pll_3 = 0x09; + const static int pll_4 = 0x0A; + const static int ref_counter_msb = 0x0B; + const static int ref_counter_lsb = 0x0C; + const static int pll_5 = 0x0D; + const static int update = 0x5A; + + int test; + int fpga; + int adc; + int dac; + int serdes; + int exp; + int tx_db; + int rx_db; +}; + +#endif //INCLUDED_USRP2_CLK_REGS_HPP diff --git a/host/lib/usrp/usrp2/usrp2_iface.cpp b/host/lib/usrp/usrp2/usrp2_iface.cpp new file mode 100644 index 000000000..123910166 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.cpp @@ -0,0 +1,378 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_regs.hpp" +#include "fw_common.h" +#include "usrp2_iface.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/types/dict.hpp> +#include <boost/thread.hpp> +#include <boost/foreach.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/tokenizer.hpp> +#include <boost/functional/hash.hpp> +#include <algorithm> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const double CTRL_RECV_TIMEOUT = 1.0; +static const size_t CTRL_RECV_RETRIES = 3; + +//custom timeout error for retry logic to catch/retry +struct timeout_error : uhd::runtime_error +{ + timeout_error(const std::string &what): + uhd::runtime_error(what) + { + //NOP + } +}; + +static const boost::uint32_t MIN_PROTO_COMPAT_SPI = 7; +static const boost::uint32_t MIN_PROTO_COMPAT_I2C = 7; +// The register compat number must reflect the protocol compatibility +// and the compatibility of the register mapping (more likely to change). +static const boost::uint32_t MIN_PROTO_COMPAT_REG = 10; +static const boost::uint32_t MIN_PROTO_COMPAT_UART = 7; + +//Define get_gpid() to get a globally unique identifier for this process. +//The gpid is implemented as a hash of the pid and a unique machine identifier. +#ifdef UHD_PLATFORM_WIN32 +#include <Windows.h> +static inline size_t get_gpid(void){ + //extract volume serial number + char szVolName[MAX_PATH+1], szFileSysName[MAX_PATH+1]; + DWORD dwSerialNumber, dwMaxComponentLen, dwFileSysFlags; + GetVolumeInformation("C:\\", szVolName, MAX_PATH, + &dwSerialNumber, &dwMaxComponentLen, + &dwFileSysFlags, szFileSysName, sizeof(szFileSysName)); + + size_t hash = 0; + boost::hash_combine(hash, GetCurrentProcessId()); + boost::hash_combine(hash, dwSerialNumber); + return hash; +} +#else +#include <unistd.h> +static inline size_t get_gpid(void){ + size_t hash = 0; + boost::hash_combine(hash, getpid()); + boost::hash_combine(hash, gethostid()); + return hash; +} +#endif + +class usrp2_iface_impl : public usrp2_iface{ +public: +/*********************************************************************** + * Structors + **********************************************************************/ + usrp2_iface_impl(udp_simple::sptr ctrl_transport): + _ctrl_transport(ctrl_transport), + _ctrl_seq_num(0), + _protocol_compat(0) //initialized below... + { + //Obtain the firmware's compat number. + //Save the response compat number for communication. + //TODO can choose to reject certain older compat numbers + usrp2_ctrl_data_t ctrl_data = usrp2_ctrl_data_t(); + ctrl_data.id = htonl(USRP2_CTRL_ID_WAZZUP_BRO); + ctrl_data = ctrl_send_and_recv(ctrl_data, 0, ~0); + if (ntohl(ctrl_data.id) != USRP2_CTRL_ID_WAZZUP_DUDE) + throw uhd::runtime_error("firmware not responding"); + _protocol_compat = ntohl(ctrl_data.proto_ver); + + mb_eeprom = mboard_eeprom_t(*this, mboard_eeprom_t::MAP_N100); + } + + ~usrp2_iface_impl(void){UHD_SAFE_CALL( + this->lock_device(false); + )} + +/*********************************************************************** + * Device locking + **********************************************************************/ + + void lock_device(bool lock){ + if (lock){ + this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_GPID, boost::uint32_t(get_gpid())); + _lock_task = task::make(boost::bind(&usrp2_iface_impl::lock_task, this)); + } + else{ + _lock_task.reset(); //shutdown the task + this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_TIME, 0); //unlock + } + } + + bool is_device_locked(void){ + //never assume lock with fpga image mismatch + if ((this->peek32(U2_REG_COMPAT_NUM_RB) >> 16) != USRP2_FPGA_COMPAT_NUM) return false; + + boost::uint32_t lock_time = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_LOCK_TIME); + boost::uint32_t lock_gpid = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_LOCK_GPID); + + //may not be the right tick rate, but this is ok for locking purposes + const boost::uint32_t lock_timeout_time = boost::uint32_t(3*100e6); + + //if the difference is larger, assume not locked anymore + if ((lock_time & 1) == 0) return false; //bit0 says unlocked + const boost::uint32_t time_diff = this->get_curr_time() - lock_time; + if (time_diff >= lock_timeout_time) return false; + + //otherwise only lock if the device hash is different that ours + return lock_gpid != boost::uint32_t(get_gpid()); + } + + void lock_task(void){ + //re-lock in task + this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_TIME, this->get_curr_time()); + //sleep for a bit + boost::this_thread::sleep(boost::posix_time::milliseconds(1500)); + } + + boost::uint32_t get_curr_time(void){ + return this->peek32(U2_REG_TIME64_LO_RB_IMM) | 1; //bit 1 says locked + } + +/*********************************************************************** + * Peek and Poke + **********************************************************************/ + void poke32(wb_addr_type addr, boost::uint32_t data){ + this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FPGA_POKE32>(addr, data); + } + + boost::uint32_t peek32(wb_addr_type addr){ + return this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FPGA_PEEK32>(addr); + } + + void poke16(wb_addr_type addr, boost::uint16_t data){ + this->get_reg<boost::uint16_t, USRP2_REG_ACTION_FPGA_POKE16>(addr, data); + } + + boost::uint16_t peek16(wb_addr_type addr){ + return this->get_reg<boost::uint16_t, USRP2_REG_ACTION_FPGA_PEEK16>(addr); + } + + template <class T, usrp2_reg_action_t action> + T get_reg(wb_addr_type addr, T data = 0){ + //setup the 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)); + out_data.data.reg_args.action = action; + + //send and recv + usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_REG); + UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_OMG_GOT_REGISTER_SO_BAD_DUDE); + return T(ntohl(in_data.data.reg_args.data)); + } + +/*********************************************************************** + * SPI + **********************************************************************/ + boost::uint32_t transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback + ){ + static const uhd::dict<spi_config_t::edge_t, int> spi_edge_to_otw = boost::assign::map_list_of + (spi_config_t::EDGE_RISE, USRP2_CLK_EDGE_RISE) + (spi_config_t::EDGE_FALL, USRP2_CLK_EDGE_FALL) + ; + + //setup the 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]; + out_data.data.spi_args.mosi_edge = spi_edge_to_otw[config.mosi_edge]; + out_data.data.spi_args.readback = (readback)? 1 : 0; + out_data.data.spi_args.num_bits = num_bits; + out_data.data.spi_args.data = htonl(data); + + //send and recv + usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_SPI); + UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_OMG_TRANSACTED_SPI_DUDE); + + return ntohl(in_data.data.spi_args.data); + } + +/*********************************************************************** + * I2C + **********************************************************************/ + 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.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(); + + //limitation of i2c transaction size + UHD_ASSERT_THROW(buf.size() <= sizeof(out_data.data.i2c_args.data)); + + //copy in the data + std::copy(buf.begin(), buf.end(), out_data.data.i2c_args.data); + + //send and recv + usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_I2C); + UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_COOL_IM_DONE_I2C_WRITE_DUDE); + } + + 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.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; + + //limitation of i2c transaction size + UHD_ASSERT_THROW(num_bytes <= sizeof(out_data.data.i2c_args.data)); + + //send and recv + usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_I2C); + UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_HERES_THE_I2C_DATA_DUDE); + UHD_ASSERT_THROW(in_data.data.i2c_args.addr = num_bytes); + + //copy out the data + byte_vector_t result(num_bytes); + std::copy(in_data.data.i2c_args.data, in_data.data.i2c_args.data + num_bytes, result.begin()); + return result; + } + +/*********************************************************************** + * Send/Recv over control + **********************************************************************/ + usrp2_ctrl_data_t ctrl_send_and_recv( + const usrp2_ctrl_data_t &out_data, + boost::uint32_t lo = USRP2_FW_COMPAT_NUM, + boost::uint32_t hi = USRP2_FW_COMPAT_NUM + ){ + boost::mutex::scoped_lock lock(_ctrl_mutex); + + for (size_t i = 0; i < CTRL_RECV_RETRIES; i++){ + try{ + return ctrl_send_and_recv_internal(out_data, lo, hi, CTRL_RECV_TIMEOUT/CTRL_RECV_RETRIES); + } + catch(const timeout_error &e){ + UHD_MSG(error) + << "Control packet attempt " << i + << ", sequence number " << _ctrl_seq_num + << ":\n" << e.what() << std::endl; + } + } + throw uhd::runtime_error("link dead: timeout waiting for control packet ACK"); + } + + usrp2_ctrl_data_t ctrl_send_and_recv_internal( + const usrp2_ctrl_data_t &out_data, + boost::uint32_t lo, boost::uint32_t hi, + const double timeout + ){ + //fill in the seq number and send + usrp2_ctrl_data_t out_copy = out_data; + out_copy.proto_ver = htonl(_protocol_compat); + out_copy.seq = htonl(++_ctrl_seq_num); + _ctrl_transport->send(boost::asio::buffer(&out_copy, sizeof(usrp2_ctrl_data_t))); + + //loop until we get the packet or timeout + boost::uint8_t usrp2_ctrl_data_in_mem[udp_simple::mtu]; //allocate max bytes for recv + const usrp2_ctrl_data_t *ctrl_data_in = reinterpret_cast<const usrp2_ctrl_data_t *>(usrp2_ctrl_data_in_mem); + while(true){ + size_t len = _ctrl_transport->recv(boost::asio::buffer(usrp2_ctrl_data_in_mem), timeout); + boost::uint32_t compat = ntohl(ctrl_data_in->proto_ver); + if(len >= sizeof(boost::uint32_t) and (hi < compat or lo > compat)){ + throw uhd::runtime_error(str(boost::format( + "\nPlease update the firmware and FPGA images for your device.\n" + "See the application notes for USRP2/N-Series for instructions.\n" + "Expected protocol compatibility number %s, but got %d:\n" + "The firmware build is not compatible with the host code build." + ) % ((lo == hi)? (boost::format("%d") % hi) : (boost::format("[%d to %d]") % lo % hi)) % compat)); + } + if (len >= sizeof(usrp2_ctrl_data_t) and ntohl(ctrl_data_in->seq) == _ctrl_seq_num){ + return *ctrl_data_in; + } + if (len == 0) break; //timeout + //didnt get seq or bad packet, continue looking... + } + throw timeout_error("no control response, possible packet loss"); + } + + rev_type get_rev(void){ + std::string hw = mb_eeprom["hardware"]; + if (hw.empty()) return USRP_NXXX; + switch (boost::lexical_cast<boost::uint16_t>(hw)){ + case 0x0300: + case 0x0301: return USRP2_REV3; + case 0x0400: return USRP2_REV4; + case 0x0A00: return USRP_N200; + case 0x0A01: return USRP_N210; + case 0x0A10: return USRP_N200_R4; + case 0x0A11: return USRP_N210_R4; + } + return USRP_NXXX; //unknown type + } + + const std::string get_cname(void){ + switch(this->get_rev()){ + case USRP2_REV3: return "USRP2-REV3"; + case USRP2_REV4: return "USRP2-REV4"; + case USRP_N200: return "USRP-N200"; + case USRP_N210: return "USRP-N210"; + case USRP_N200_R4: return "USRP-N200-REV4"; + case USRP_N210_R4: return "USRP-N210-REV4"; + case USRP_NXXX: return "USRP-N???"; + } + UHD_THROW_INVALID_CODE_PATH(); + } + + const std::string get_fw_version_string(void){ + boost::uint32_t minor = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_VER_MINOR); + return str(boost::format("%u.%u") % _protocol_compat % minor); + } + +private: + //this lovely lady makes it all possible + udp_simple::sptr _ctrl_transport; + + //used in send/recv + boost::mutex _ctrl_mutex; + boost::uint32_t _ctrl_seq_num; + boost::uint32_t _protocol_compat; + + //lock thread stuff + task::sptr _lock_task; +}; + +/*********************************************************************** + * Public make function for usrp2 interface + **********************************************************************/ +usrp2_iface::sptr usrp2_iface::make(udp_simple::sptr ctrl_transport){ + return usrp2_iface::sptr(new usrp2_iface_impl(ctrl_transport)); +} + diff --git a/host/lib/usrp/usrp2/usrp2_iface.hpp b/host/lib/usrp/usrp2/usrp2_iface.hpp new file mode 100644 index 000000000..9aa1a16aa --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.hpp @@ -0,0 +1,76 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_IFACE_HPP +#define INCLUDED_USRP2_IFACE_HPP + +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/function.hpp> +#include "usrp2_regs.hpp" +#include "wb_iface.hpp" +#include <string> + +/*! + * The usrp2 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp2_iface : public wb_iface, public uhd::spi_iface, public uhd::i2c_iface{ +public: + typedef boost::shared_ptr<usrp2_iface> sptr; + /*! + * Make a new usrp2 interface with the control transport. + * \param ctrl_transport the udp transport object + * \return a new usrp2 interface object + */ + static sptr make(uhd::transport::udp_simple::sptr ctrl_transport); + + //! The list of possible revision types + enum rev_type { + USRP2_REV3 = 3, + USRP2_REV4 = 4, + USRP_N200 = 200, + USRP_N200_R4 = 201, + USRP_N210 = 210, + USRP_N210_R4 = 211, + USRP_NXXX = 0 + }; + + //! Get the revision type for this device + virtual rev_type get_rev(void) = 0; + + //! Get the canonical name for this device + virtual const std::string get_cname(void) = 0; + + //! Lock the device to this iface + virtual void lock_device(bool lock) = 0; + + //! Is this device locked? + virtual bool is_device_locked(void) = 0; + + //! A version string for firmware + virtual const std::string get_fw_version_string(void) = 0; + + //motherboard eeprom map structure + uhd::usrp::mboard_eeprom_t mb_eeprom; +}; + +#endif /* INCLUDED_USRP2_IFACE_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp new file mode 100644 index 000000000..ddae34b53 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -0,0 +1,763 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_impl.hpp" +#include "fw_common.h" +#include "apply_corrections.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; + +/*********************************************************************** + * Discovery over the udp transport + **********************************************************************/ +static device_addrs_t usrp2_find(const device_addr_t &hint_){ + //handle the multi-device discovery + device_addrs_t hints = separate_device_addr(hint_); + if (hints.size() > 1){ + device_addrs_t found_devices; + BOOST_FOREACH(const device_addr_t &hint_i, hints){ + device_addrs_t found_devices_i = usrp2_find(hint_i); + if (found_devices_i.size() != 1) throw uhd::value_error(str(boost::format( + "Could not resolve device hint \"%s\" to a single device." + ) % hint_i.to_string())); + found_devices.push_back(found_devices_i[0]); + } + return device_addrs_t(1, combine_device_addrs(found_devices)); + } + + //initialize the hint for a single device case + UHD_ASSERT_THROW(hints.size() <= 1); + hints.resize(1); //in case it was empty + device_addr_t hint = hints[0]; + device_addrs_t usrp2_addrs; + + //return an empty list of addresses when type is set to non-usrp2 + if (hint.has_key("type") and hint["type"] != "usrp2") return usrp2_addrs; + + //if no address was specified, send a broadcast on each interface + if (not hint.has_key("addr")){ + BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()){ + //avoid the loopback device + if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + + //create a new hint with this broadcast address + device_addr_t new_hint = hint; + new_hint["addr"] = if_addrs.bcast; + + //call discover with the new hint and append results + device_addrs_t new_usrp2_addrs = usrp2_find(new_hint); + usrp2_addrs.insert(usrp2_addrs.begin(), + new_usrp2_addrs.begin(), new_usrp2_addrs.end() + ); + } + return usrp2_addrs; + } + + //Create a UDP transport to communicate: + //Some devices will cause a throw when opened for a broadcast address. + //We print and recover so the caller can loop through all bcast addrs. + udp_simple::sptr udp_transport; + try{ + udp_transport = udp_simple::make_broadcast(hint["addr"], BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT)); + } + catch(const std::exception &e){ + UHD_MSG(error) << boost::format("Cannot open UDP transport on %s\n%s") % hint["addr"] % e.what() << std::endl; + return usrp2_addrs; //dont throw, but return empty address so caller can insert + } + + //send a hello control packet + usrp2_ctrl_data_t ctrl_data_out = usrp2_ctrl_data_t(); + ctrl_data_out.proto_ver = uhd::htonx<boost::uint32_t>(USRP2_FW_COMPAT_NUM); + ctrl_data_out.id = uhd::htonx<boost::uint32_t>(USRP2_CTRL_ID_WAZZUP_BRO); + udp_transport->send(boost::asio::buffer(&ctrl_data_out, sizeof(ctrl_data_out))); + + //loop and recieve until the timeout + boost::uint8_t usrp2_ctrl_data_in_mem[udp_simple::mtu]; //allocate max bytes for recv + const usrp2_ctrl_data_t *ctrl_data_in = reinterpret_cast<const usrp2_ctrl_data_t *>(usrp2_ctrl_data_in_mem); + while(true){ + size_t len = udp_transport->recv(asio::buffer(usrp2_ctrl_data_in_mem)); + if (len > offsetof(usrp2_ctrl_data_t, data) and ntohl(ctrl_data_in->id) == USRP2_CTRL_ID_WAZZUP_DUDE){ + + //make a boost asio ipv4 with the raw addr in host byte order + device_addr_t new_addr; + new_addr["type"] = "usrp2"; + //We used to get the address from the control packet. + //Now now uses the socket itself to yield the address. + //boost::asio::ip::address_v4 ip_addr(ntohl(ctrl_data_in->data.ip_addr)); + //new_addr["addr"] = ip_addr.to_string(); + new_addr["addr"] = udp_transport->get_recv_addr(); + + //Attempt to read the name from the EEPROM and perform filtering. + //This operation can throw due to compatibility mismatch. + try{ + usrp2_iface::sptr iface = usrp2_iface::make(udp_simple::make_connected( + new_addr["addr"], BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) + )); + if (iface->is_device_locked()) continue; //ignore locked devices + mboard_eeprom_t mb_eeprom = iface->mb_eeprom; + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = mb_eeprom["serial"]; + } + catch(const std::exception &){ + //set these values as empty string so the device may still be found + //and the filter's below can still operate on the discovered device + new_addr["name"] = ""; + new_addr["serial"] = ""; + } + + //filter the discovered device below by matching optional keys + if ( + (not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) + ){ + usrp2_addrs.push_back(new_addr); + } + + //dont break here, it will exit the while loop + //just continue on to the next loop iteration + } + if (len == 0) break; //timeout + } + + return usrp2_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp2_make(const device_addr_t &device_addr){ + return device::sptr(new usrp2_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_usrp2_device){ + device::register_device(&usrp2_find, &usrp2_make); +} + +/*********************************************************************** + * MTU Discovery + **********************************************************************/ +struct mtu_result_t{ + size_t recv_mtu, send_mtu; +}; + +static mtu_result_t determine_mtu(const std::string &addr, const mtu_result_t &user_mtu){ + udp_simple::sptr udp_sock = udp_simple::make_connected( + addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) + ); + + //The FPGA offers 4K buffers, and the user may manually request this. + //However, multiple simultaneous receives (2DSP slave + 2DSP master), + //require that buffering to be used internally, and this is a safe setting. + std::vector<boost::uint8_t> buffer(std::max(user_mtu.recv_mtu, user_mtu.send_mtu)); + usrp2_ctrl_data_t *ctrl_data = reinterpret_cast<usrp2_ctrl_data_t *>(&buffer.front()); + static const double echo_timeout = 0.020; //20 ms + + //test holler - check if its supported in this fw version + ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); + ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); + ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t)); + udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t))); + udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); + if (ntohl(ctrl_data->id) != USRP2_CTRL_ID_HOLLER_BACK_DUDE) + throw uhd::not_implemented_error("holler protocol not implemented"); + + size_t min_recv_mtu = sizeof(usrp2_ctrl_data_t), max_recv_mtu = user_mtu.recv_mtu; + size_t min_send_mtu = sizeof(usrp2_ctrl_data_t), max_send_mtu = user_mtu.send_mtu; + + while (min_recv_mtu < max_recv_mtu){ + + size_t test_mtu = (max_recv_mtu/2 + min_recv_mtu/2 + 3) & ~3; + + ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); + ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); + ctrl_data->data.echo_args.len = htonl(test_mtu); + udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t))); + + size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); + + if (len >= test_mtu) min_recv_mtu = test_mtu; + else max_recv_mtu = test_mtu - 4; + + } + + while (min_send_mtu < max_send_mtu){ + + size_t test_mtu = (max_send_mtu/2 + min_send_mtu/2 + 3) & ~3; + + ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); + ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); + ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t)); + udp_sock->send(boost::asio::buffer(buffer, test_mtu)); + + size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); + if (len >= sizeof(usrp2_ctrl_data_t)) len = ntohl(ctrl_data->data.echo_args.len); + + if (len >= test_mtu) min_send_mtu = test_mtu; + else max_send_mtu = test_mtu - 4; + } + + mtu_result_t mtu; + mtu.recv_mtu = min_recv_mtu; + mtu.send_mtu = min_send_mtu; + return mtu; +} + +/*********************************************************************** + * Helpers + **********************************************************************/ +static zero_copy_if::sptr make_xport( + const std::string &addr, + const std::string &port, + const device_addr_t &hints, + const std::string &filter +){ + + //only copy hints that contain the filter word + device_addr_t filtered_hints; + BOOST_FOREACH(const std::string &key, hints.keys()){ + if (key.find(filter) == std::string::npos) continue; + filtered_hints[key] = hints[key]; + } + + //make the transport object with the filtered hints + zero_copy_if::sptr xport = udp_zero_copy::make(addr, port, filtered_hints); + + //Send a small data packet so the usrp2 knows the udp source port. + //This setup must happen before further initialization occurs + //or the async update packets will cause ICMP destination unreachable. + static const boost::uint32_t data[2] = { + uhd::htonx(boost::uint32_t(0 /* don't care seq num */)), + uhd::htonx(boost::uint32_t(USRP2_INVALID_VRT_HEADER)) + }; + transport::managed_send_buffer::sptr send_buff = xport->get_send_buff(); + std::memcpy(send_buff->cast<void*>(), &data, sizeof(data)); + send_buff->commit(sizeof(data)); + + return xport; +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp2_impl::usrp2_impl(const device_addr_t &_device_addr){ + UHD_MSG(status) << "Opening a USRP2/N-Series device..." << std::endl; + device_addr_t device_addr = _device_addr; + + //setup the dsp transport hints (default to a large recv buff) + if (not device_addr.has_key("recv_buff_size")){ + #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + //limit buffer resize on macos or it will error + device_addr["recv_buff_size"] = "1e6"; + #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) + //set to half-a-second of buffering at max rate + device_addr["recv_buff_size"] = "50e6"; + #endif + } + if (not device_addr.has_key("send_buff_size")){ + //The buffer should be the size of the SRAM on the device, + //because we will never commit more than the SRAM can hold. + device_addr["send_buff_size"] = boost::lexical_cast<std::string>(USRP2_SRAM_BYTES); + } + + device_addrs_t device_args = separate_device_addr(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<double>("recv_frame_size", udp_simple::mtu)); + user_mtu.send_mtu = size_t(device_addr.cast<double>("send_frame_size", udp_simple::mtu)); + + try{ + //calculate the minimum send and recv mtu of all devices + mtu_result_t mtu = determine_mtu(device_args[0]["addr"], user_mtu); + for (size_t i = 1; i < device_args.size(); i++){ + mtu_result_t mtu_i = determine_mtu(device_args[i]["addr"], user_mtu); + mtu.recv_mtu = std::min(mtu.recv_mtu, mtu_i.recv_mtu); + mtu.send_mtu = std::min(mtu.send_mtu, mtu_i.send_mtu); + } + + device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(mtu.recv_mtu); + device_addr["send_frame_size"] = boost::lexical_cast<std::string>(mtu.send_mtu); + + UHD_MSG(status) << boost::format("Current recv frame size: %d bytes") % mtu.recv_mtu << std::endl; + UHD_MSG(status) << boost::format("Current send frame size: %d bytes") % mtu.send_mtu << std::endl; + } + catch(const uhd::not_implemented_error &){ + //just ignore this error, makes older fw work... + } + + device_args = separate_device_addr(device_addr); //update args for new frame sizes + + //////////////////////////////////////////////////////////////////// + // create controller objects and initialize the properties tree + //////////////////////////////////////////////////////////////////// + _tree = property_tree::make(); + _tree->create<std::string>("/name").set("USRP2 / N-Series Device"); + + for (size_t mbi = 0; mbi < device_args.size(); mbi++){ + const device_addr_t device_args_i = device_args[mbi]; + const std::string mb = boost::lexical_cast<std::string>(mbi); + const std::string addr = device_args_i["addr"]; + const fs_path mb_path = "/mboards/" + mb; + + //////////////////////////////////////////////////////////////// + // create the iface that controls i2c, spi, uart, and wb + //////////////////////////////////////////////////////////////// + _mbc[mb].iface = usrp2_iface::make(udp_simple::make_connected( + addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) + )); + _tree->create<std::string>(mb_path / "name").set(_mbc[mb].iface->get_cname()); + _tree->create<std::string>(mb_path / "fw_version").set(_mbc[mb].iface->get_fw_version_string()); + + //check the fpga compatibility number + const boost::uint32_t fpga_compat_num = _mbc[mb].iface->peek32(U2_REG_COMPAT_NUM_RB); + boost::uint16_t fpga_major = fpga_compat_num >> 16, fpga_minor = fpga_compat_num & 0xffff; + if (fpga_major == 0){ //old version scheme + fpga_major = fpga_minor; + fpga_minor = 0; + } + if (fpga_major != USRP2_FPGA_COMPAT_NUM){ + throw uhd::runtime_error(str(boost::format( + "\nPlease update the firmware and FPGA images for your device.\n" + "See the application notes for USRP2/N-Series for instructions.\n" + "Expected FPGA compatibility number %d, but got %d:\n" + "The FPGA build is not compatible with the host code build." + ) % int(USRP2_FPGA_COMPAT_NUM) % fpga_major)); + } + _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % fpga_major % fpga_minor)); + + //lock the device/motherboard to this process + _mbc[mb].iface->lock_device(true); + + //////////////////////////////////////////////////////////////// + // construct transports for RX and TX DSPs + //////////////////////////////////////////////////////////////// + UHD_LOG << "Making transport for RX DSP0..." << std::endl; + _mbc[mb].rx_dsp_xports.push_back(make_xport( + addr, BOOST_STRINGIZE(USRP2_UDP_RX_DSP0_PORT), device_args_i, "recv" + )); + UHD_LOG << "Making transport for RX DSP1..." << std::endl; + _mbc[mb].rx_dsp_xports.push_back(make_xport( + addr, BOOST_STRINGIZE(USRP2_UDP_RX_DSP1_PORT), device_args_i, "recv" + )); + UHD_LOG << "Making transport for TX DSP0..." << std::endl; + _mbc[mb].tx_dsp_xport = make_xport( + addr, BOOST_STRINGIZE(USRP2_UDP_TX_DSP0_PORT), device_args_i, "send" + ); + //set the filter on the router to take dsp data from this port + _mbc[mb].iface->poke32(U2_REG_ROUTER_CTRL_PORTS, USRP2_UDP_TX_DSP0_PORT); + + //////////////////////////////////////////////////////////////// + // setup the mboard eeprom + //////////////////////////////////////////////////////////////// + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(_mbc[mb].iface->mb_eeprom) + .subscribe(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); + + //////////////////////////////////////////////////////////////// + // create clock control objects + //////////////////////////////////////////////////////////////// + _mbc[mb].clock = usrp2_clock_ctrl::make(_mbc[mb].iface); + _tree->create<double>(mb_path / "tick_rate") + .publish(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) + .subscribe(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); + + //////////////////////////////////////////////////////////////// + // create codec control objects + //////////////////////////////////////////////////////////////// + const fs_path rx_codec_path = mb_path / "rx_codecs/A"; + const fs_path tx_codec_path = mb_path / "tx_codecs/A"; + _tree->create<int>(rx_codec_path / "gains"); //phony property so this dir exists + _tree->create<int>(tx_codec_path / "gains"); //phony property so this dir exists + _mbc[mb].codec = usrp2_codec_ctrl::make(_mbc[mb].iface); + switch(_mbc[mb].iface->get_rev()){ + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4:{ + _tree->create<std::string>(rx_codec_path / "name").set("ads62p44"); + _tree->create<meta_range_t>(rx_codec_path / "gains/digital/range").set(meta_range_t(0, 6.0, 0.5)); + _tree->create<double>(rx_codec_path / "gains/digital/value") + .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); + _tree->create<meta_range_t>(rx_codec_path / "gains/fine/range").set(meta_range_t(0, 0.5, 0.05)); + _tree->create<double>(rx_codec_path / "gains/fine/value") + .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); + }break; + + case usrp2_iface::USRP2_REV3: + case usrp2_iface::USRP2_REV4: + _tree->create<std::string>(rx_codec_path / "name").set("ltc2284"); + break; + + case usrp2_iface::USRP_NXXX: + _tree->create<std::string>(rx_codec_path / "name").set("??????"); + break; + } + _tree->create<std::string>(tx_codec_path / "name").set("ad9777"); + + //////////////////////////////////////////////////////////////// + // create gpsdo control objects + //////////////////////////////////////////////////////////////// + if (_mbc[mb].iface->mb_eeprom["gpsdo"] == "internal"){ + _mbc[mb].gps = gps_ctrl::make(udp_simple::make_uart(udp_simple::make_connected( + addr, BOOST_STRINGIZE(USRP2_UDP_UART_GPS_PORT) + ))); + if(_mbc[mb].gps->gps_detected()) { + BOOST_FOREACH(const std::string &name, _mbc[mb].gps->get_sensors()){ + _tree->create<sensor_value_t>(mb_path / "sensors" / name) + .publish(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); + } + } + } + + //////////////////////////////////////////////////////////////// + // and do the misc mboard sensors + //////////////////////////////////////////////////////////////// + _tree->create<sensor_value_t>(mb_path / "sensors/mimo_locked") + .publish(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); + _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") + .publish(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); + + //////////////////////////////////////////////////////////////// + // create frontend control objects + //////////////////////////////////////////////////////////////// + _mbc[mb].rx_fe = rx_frontend_core_200::make( + _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_FRONT) + ); + _mbc[mb].tx_fe = tx_frontend_core_200::make( + _mbc[mb].iface, U2_REG_SR_ADDR(SR_TX_FRONT) + ); + + _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") + .subscribe(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); + _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") + .subscribe(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); + + const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; + const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; + + _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") + .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1)) + .set(true); + _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") + .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1)) + .set(std::polar<double>(1.0, 0.0)); + _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") + .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1)) + .set(std::polar<double>(1.0, 0.0)); + + //////////////////////////////////////////////////////////////// + // create rx dsp control objects + //////////////////////////////////////////////////////////////// + _mbc[mb].rx_dsps.push_back(rx_dsp_core_200::make( + _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_DSP0), U2_REG_SR_ADDR(SR_RX_CTRL0), USRP2_RX_SID_BASE + 0, true + )); + _mbc[mb].rx_dsps.push_back(rx_dsp_core_200::make( + _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_DSP1), U2_REG_SR_ADDR(SR_RX_CTRL1), USRP2_RX_SID_BASE + 1, true + )); + for (size_t dspno = 0; dspno < _mbc[mb].rx_dsps.size(); dspno++){ + _mbc[mb].rx_dsps[dspno]->set_link_rate(USRP2_LINK_RATE_BPS); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); + fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); + _tree->create<meta_range_t>(rx_dsp_path / "rate/range") + .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno])); + _tree->create<double>(rx_dsp_path / "rate/value") + .set(1e6) //some default + .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) + .subscribe(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1)); + _tree->create<double>(rx_dsp_path / "freq/value") + .coerce(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); + _tree->create<meta_range_t>(rx_dsp_path / "freq/range") + .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); + } + + //////////////////////////////////////////////////////////////// + // create tx dsp control objects + //////////////////////////////////////////////////////////////// + _mbc[mb].tx_dsp = tx_dsp_core_200::make( + _mbc[mb].iface, U2_REG_SR_ADDR(SR_TX_DSP), U2_REG_SR_ADDR(SR_TX_CTRL), USRP2_TX_ASYNC_SID + ); + _mbc[mb].tx_dsp->set_link_rate(USRP2_LINK_RATE_BPS); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); + _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") + .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp)); + _tree->create<double>(mb_path / "tx_dsps/0/rate/value") + .set(1e6) //some default + .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) + .subscribe(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1)); + _tree->create<double>(mb_path / "tx_dsps/0/freq/value") + .coerce(boost::bind(&usrp2_impl::set_tx_dsp_freq, this, mb, _1)); + _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") + .publish(boost::bind(&usrp2_impl::get_tx_dsp_freq_range, this, mb)); + + //setup dsp flow control + const double ups_per_sec = device_args_i.cast<double>("ups_per_sec", 20); + const size_t send_frame_size = _mbc[mb].tx_dsp_xport->get_send_frame_size(); + const double ups_per_fifo = device_args_i.cast<double>("ups_per_fifo", 8.0); + _mbc[mb].tx_dsp->set_updates( + (ups_per_sec > 0.0)? size_t(100e6/*approx tick rate*//ups_per_sec) : 0, + (ups_per_fifo > 0.0)? size_t(USRP2_SRAM_BYTES/ups_per_fifo/send_frame_size) : 0 + ); + + //////////////////////////////////////////////////////////////// + // create time control objects + //////////////////////////////////////////////////////////////// + time64_core_200::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_hi_now = U2_REG_TIME64_HI_RB_IMM; + time64_rb_bases.rb_lo_now = U2_REG_TIME64_LO_RB_IMM; + time64_rb_bases.rb_hi_pps = U2_REG_TIME64_HI_RB_PPS; + time64_rb_bases.rb_lo_pps = U2_REG_TIME64_LO_RB_PPS; + _mbc[mb].time64 = time64_core_200::make( + _mbc[mb].iface, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases, mimo_clock_sync_delay_cycles + ); + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); + _tree->create<time_spec_t>(mb_path / "time/now") + .publish(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) + .subscribe(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); + _tree->create<time_spec_t>(mb_path / "time/pps") + .publish(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) + .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); + //setup time source props + _tree->create<std::string>(mb_path / "time_source/value") + .subscribe(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)); + _tree->create<std::vector<std::string> >(mb_path / "time_source/options") + .publish(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); + //setup reference source props + _tree->create<std::string>(mb_path / "clock_source/value") + .subscribe(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)); + static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("mimo"); + _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); + + //////////////////////////////////////////////////////////////////// + // create user-defined control objects + //////////////////////////////////////////////////////////////////// + _mbc[mb].user = user_settings_core_200::make(_mbc[mb].iface, U2_REG_SR_ADDR(SR_USER_REGS)); + _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") + .subscribe(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1)); + + //////////////////////////////////////////////////////////////// + // create dboard control objects + //////////////////////////////////////////////////////////////// + + //read the dboard eeprom to extract the dboard ids + dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; + rx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_RX_DB); + tx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB); + gdb_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5); + + //create the properties and register subscribers + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") + .set(rx_db_eeprom) + .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") + .set(tx_db_eeprom) + .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); + _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") + .set(gdb_eeprom) + .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); + + //create a new dboard interface and manager + _mbc[mb].dboard_iface = make_usrp2_dboard_iface(_mbc[mb].iface, _mbc[mb].clock); + _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_mbc[mb].dboard_iface); + _mbc[mb].dboard_manager = dboard_manager::make( + rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, + _mbc[mb].dboard_iface, _tree->subtree(mb_path / "dboards/A") + ); + + //bind frontend corrections to the dboard freq props + const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ + _tree->access<double>(db_tx_fe_path / name / "freq" / "value") + .subscribe(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1)); + } + const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ + _tree->access<double>(db_rx_fe_path / name / "freq" / "value") + .subscribe(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1)); + } + } + + //initialize io handling + this->io_init(); + + //do some post-init tasks + this->update_rates(); + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + fs_path root = "/mboards/" + mb; + + //reset cordic rates and their properties to zero + BOOST_FOREACH(const std::string &name, _tree->list(root / "rx_dsps")){ + _tree->access<double>(root / "rx_dsps" / name / "freq" / "value").set(0.0); + } + BOOST_FOREACH(const std::string &name, _tree->list(root / "tx_dsps")){ + _tree->access<double>(root / "tx_dsps" / name / "freq" / "value").set(0.0); + } + + _tree->access<subdev_spec_t>(root / "rx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(root / "dboards/A/rx_frontends").at(0))); + _tree->access<subdev_spec_t>(root / "tx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(root / "dboards/A/tx_frontends").at(0))); + _tree->access<std::string>(root / "clock_source/value").set("internal"); + _tree->access<std::string>(root / "time_source/value").set("none"); + + //GPS installed: use external ref, time, and init time spec + if (_mbc[mb].gps.get() and _mbc[mb].gps->gps_detected()){ + UHD_MSG(status) << "Setting references to the internal GPSDO" << std::endl; + _tree->access<std::string>(root / "time_source/value").set("external"); + _tree->access<std::string>(root / "clock_source/value").set("external"); + UHD_MSG(status) << "Initializing time to the internal GPSDO" << std::endl; + _mbc[mb].time64->set_time_next_pps(time_spec_t(time_t(_mbc[mb].gps->get_sensor("gps_time").to_int()+1))); + } + } + +} + +usrp2_impl::~usrp2_impl(void){UHD_SAFE_CALL( + BOOST_FOREACH(const std::string &mb, _mbc.keys()){ + _mbc[mb].tx_dsp->set_updates(0, 0); + } +)} + +void usrp2_impl::set_mb_eeprom(const std::string &mb, const uhd::usrp::mboard_eeprom_t &mb_eeprom){ + mb_eeprom.commit(*(_mbc[mb].iface), mboard_eeprom_t::MAP_N100); +} + +void usrp2_impl::set_db_eeprom(const std::string &mb, const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ + if (type == "rx") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_RX_DB); + if (type == "tx") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB); + if (type == "gdb") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5); +} + +sensor_value_t usrp2_impl::get_mimo_locked(const std::string &mb){ + const bool lock = (_mbc[mb].iface->peek32(U2_REG_IRQ_RB) & (1<<10)) != 0; + return sensor_value_t("MIMO", lock, "locked", "unlocked"); +} + +sensor_value_t usrp2_impl::get_ref_locked(const std::string &mb){ + const bool lock = (_mbc[mb].iface->peek32(U2_REG_IRQ_RB) & (1<<11)) != 0; + return sensor_value_t("Ref", lock, "locked", "unlocked"); +} + +void usrp2_impl::set_rx_fe_corrections(const std::string &mb, const double lo_freq){ + apply_rx_fe_corrections(this->get_tree()->subtree("/mboards/" + mb), "A", lo_freq); +} + +void usrp2_impl::set_tx_fe_corrections(const std::string &mb, const double lo_freq){ + apply_tx_fe_corrections(this->get_tree()->subtree("/mboards/" + mb), "A", lo_freq); +} + +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> + +double usrp2_impl::set_tx_dsp_freq(const std::string &mb, const double freq_){ + double new_freq = freq_; + const double tick_rate = _tree->access<double>("/mboards/"+mb+"/tick_rate").get(); + + //calculate the DAC shift (multiples of rate) + const int sign = boost::math::sign(new_freq); + const int zone = std::min(boost::math::iround(new_freq/tick_rate), 2); + const double dac_shift = sign*zone*tick_rate; + new_freq -= dac_shift; //update FPGA DSP target freq + + //set the DAC shift (modulation mode) + if (zone == 0) _mbc[mb].codec->set_tx_mod_mode(0); //no shift + else _mbc[mb].codec->set_tx_mod_mode(sign*4/zone); //DAC interp = 4 + + return _mbc[mb].tx_dsp->set_freq(new_freq) + dac_shift; //actual freq +} + +meta_range_t usrp2_impl::get_tx_dsp_freq_range(const std::string &mb){ + const double tick_rate = _tree->access<double>("/mboards/"+mb+"/tick_rate").get(); + const meta_range_t dsp_range = _mbc[mb].tx_dsp->get_freq_range(); + return meta_range_t(dsp_range.start() - tick_rate*2, dsp_range.stop() + tick_rate*2, dsp_range.step()); +} + +void usrp2_impl::update_clock_source(const std::string &mb, const std::string &source){ + //clock source ref 10mhz + switch(_mbc[mb].iface->get_rev()){ + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + if (source == "internal") _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x12); + else if (source == "external") _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C); + else if (source == "mimo") _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x15); + else throw uhd::value_error("unhandled clock configuration reference source: " + source); + _mbc[mb].clock->enable_external_ref(true); //USRP2P has an internal 10MHz TCXO + break; + + case usrp2_iface::USRP2_REV3: + case usrp2_iface::USRP2_REV4: + if (source == "internal") _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x10); + else if (source == "external") _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C); + else if (source == "mimo") _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x15); + else throw uhd::value_error("unhandled clock configuration reference source: " + source); + _mbc[mb].clock->enable_external_ref(source != "internal"); + break; + + case usrp2_iface::USRP_NXXX: break; + } + + //always drive the clock over serdes if not locking to it + _mbc[mb].clock->enable_mimo_clock_out(source != "mimo"); + + //set the mimo clock delay over the serdes + if (source != "mimo"){ + switch(_mbc[mb].iface->get_rev()){ + case usrp2_iface::USRP_N200: + case usrp2_iface::USRP_N210: + case usrp2_iface::USRP_N200_R4: + case usrp2_iface::USRP_N210_R4: + _mbc[mb].clock->set_mimo_clock_delay(mimo_clock_delay_usrp_n2xx); + break; + + case usrp2_iface::USRP2_REV4: + _mbc[mb].clock->set_mimo_clock_delay(mimo_clock_delay_usrp2_rev4); + break; + + default: break; //not handled + } + } +} diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp new file mode 100644 index 000000000..882a61f80 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -0,0 +1,134 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_IMPL_HPP +#define INCLUDED_USRP2_IMPL_HPP + +#include "usrp2_iface.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "rx_dsp_core_200.hpp" +#include "tx_dsp_core_200.hpp" +#include "time64_core_200.hpp" +#include "user_settings_core_200.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/device.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <boost/weak_ptr.hpp> + +static const double USRP2_LINK_RATE_BPS = 1000e6/8; +static const double mimo_clock_delay_usrp2_rev4 = 4.18e-9; +static const double mimo_clock_delay_usrp_n2xx = 3.55e-9; +static const size_t mimo_clock_sync_delay_cycles = 138; +static const size_t USRP2_SRAM_BYTES = size_t(1 << 20); +static const boost::uint32_t USRP2_TX_ASYNC_SID = 2; +static const boost::uint32_t USRP2_RX_SID_BASE = 3; + +/*! + * Make a usrp2 dboard interface. + * \param iface the usrp2 interface object + * \param clk_ctrl the clock control object + * \return a sptr to a new dboard interface + */ +uhd::usrp::dboard_iface::sptr make_usrp2_dboard_iface( + usrp2_iface::sptr iface, + usrp2_clock_ctrl::sptr clk_ctrl +); + +/*! + * USRP2 implementation guts: + * The implementation details are encapsulated here. + * Handles device properties and streaming... + */ +class usrp2_impl : public uhd::device{ +public: + usrp2_impl(const uhd::device_addr_t &); + ~usrp2_impl(void); + + //the io interface + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + bool recv_async_msg(uhd::async_metadata_t &, double); + +private: + uhd::property_tree::sptr _tree; + struct mb_container_type{ + usrp2_iface::sptr iface; + usrp2_clock_ctrl::sptr clock; + usrp2_codec_ctrl::sptr codec; + uhd::gps_ctrl::sptr gps; + rx_frontend_core_200::sptr rx_fe; + tx_frontend_core_200::sptr tx_fe; + std::vector<rx_dsp_core_200::sptr> rx_dsps; + std::vector<boost::weak_ptr<uhd::rx_streamer> > rx_streamers; + std::vector<boost::weak_ptr<uhd::tx_streamer> > tx_streamers; + tx_dsp_core_200::sptr tx_dsp; + time64_core_200::sptr time64; + user_settings_core_200::sptr user; + std::vector<uhd::transport::zero_copy_if::sptr> rx_dsp_xports; + uhd::transport::zero_copy_if::sptr tx_dsp_xport; + uhd::usrp::dboard_manager::sptr dboard_manager; + uhd::usrp::dboard_iface::sptr dboard_iface; + size_t rx_chan_occ, tx_chan_occ; + mb_container_type(void): rx_chan_occ(0), tx_chan_occ(0){} + }; + uhd::dict<std::string, mb_container_type> _mbc; + + void set_mb_eeprom(const std::string &, const uhd::usrp::mboard_eeprom_t &); + void set_db_eeprom(const std::string &, const std::string &, const uhd::usrp::dboard_eeprom_t &); + + uhd::sensor_value_t get_mimo_locked(const std::string &); + uhd::sensor_value_t get_ref_locked(const std::string &); + + void set_rx_fe_corrections(const std::string &mb, const double); + void set_tx_fe_corrections(const std::string &mb, const double); + + //device properties interface + uhd::property_tree::sptr get_tree(void) const{ + return _tree; + } + + //io impl methods and members + UHD_PIMPL_DECL(io_impl) _io_impl; + void io_init(void); + void update_tick_rate(const double rate); + void update_rx_samp_rate(const std::string &, const size_t, const double rate); + void update_tx_samp_rate(const std::string &, const size_t, const double rate); + void update_rates(void); + //update spec methods are coercers until we only accept db_name == A + void update_rx_subdev_spec(const std::string &, const uhd::usrp::subdev_spec_t &); + void update_tx_subdev_spec(const std::string &, const uhd::usrp::subdev_spec_t &); + double set_tx_dsp_freq(const std::string &, const double); + uhd::meta_range_t get_tx_dsp_freq_range(const std::string &); + void update_clock_source(const std::string &, const std::string &); +}; + +#endif /* INCLUDED_USRP2_IMPL_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_regs.hpp b/host/lib/usrp/usrp2/usrp2_regs.hpp new file mode 100644 index 000000000..e14798ecb --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_regs.hpp @@ -0,0 +1,107 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_REGS_HPP +#define INCLUDED_USRP2_REGS_HPP + +//////////////////////////////////////////////////////////////////////// +// Define slave bases +//////////////////////////////////////////////////////////////////////// +#define ROUTER_RAM_BASE 0x4000 +#define SPI_BASE 0x5000 +#define I2C_BASE 0x5400 +#define GPIO_BASE 0x5800 +#define READBACK_BASE 0x5C00 +#define ETH_BASE 0x6000 +#define SETTING_REGS_BASE 0x7000 +#define PIC_BASE 0x8000 +#define UART_BASE 0x8800 +#define ATR_BASE 0x8C00 + +//////////////////////////////////////////////////////////////////////// +// Setting register offsets +//////////////////////////////////////////////////////////////////////// +#define SR_MISC 0 // 7 regs +#define SR_SIMTIMER 8 // 2 +#define SR_TIME64 10 // 6 +#define SR_BUF_POOL 16 // 4 +#define SR_USER_REGS 20 // 2 +#define SR_RX_FRONT 24 // 5 +#define SR_RX_CTRL0 32 // 9 +#define SR_RX_DSP0 48 // 7 +#define SR_RX_CTRL1 80 // 9 +#define SR_RX_DSP1 96 // 7 + +#define SR_TX_FRONT 128 // ? +#define SR_TX_CTRL 144 // 6 +#define SR_TX_DSP 160 // 5 + +#define SR_GPIO 184 +#define SR_UDP_SM 192 // 64 + +#define U2_REG_SR_ADDR(sr) (SETTING_REGS_BASE + (4 * (sr))) + +#define U2_REG_ROUTER_CTRL_PORTS U2_REG_SR_ADDR(SR_BUF_POOL) + 8 + +///////////////////////////////////////////////// +// SPI Slave Constants +//////////////////////////////////////////////// +// Masks for controlling different peripherals +#define SPI_SS_AD9510 1 +#define SPI_SS_AD9777 2 +#define SPI_SS_RX_DAC 4 +#define SPI_SS_RX_ADC 8 +#define SPI_SS_RX_DB 16 +#define SPI_SS_TX_DAC 32 +#define SPI_SS_TX_ADC 64 +#define SPI_SS_TX_DB 128 +#define SPI_SS_ADS62P44 256 //for usrp2p + +///////////////////////////////////////////////// +// Misc Control +//////////////////////////////////////////////// +#define U2_REG_MISC_CTRL_CLOCK U2_REG_SR_ADDR(0) +#define U2_REG_MISC_CTRL_SERDES U2_REG_SR_ADDR(1) +#define U2_REG_MISC_CTRL_ADC U2_REG_SR_ADDR(2) +#define U2_REG_MISC_CTRL_LEDS U2_REG_SR_ADDR(3) +#define U2_REG_MISC_CTRL_PHY U2_REG_SR_ADDR(4) +#define U2_REG_MISC_CTRL_DBG_MUX U2_REG_SR_ADDR(5) +#define U2_REG_MISC_CTRL_RAM_PAGE U2_REG_SR_ADDR(6) +#define U2_REG_MISC_CTRL_FLUSH_ICACHE U2_REG_SR_ADDR(7) +#define U2_REG_MISC_CTRL_LED_SRC U2_REG_SR_ADDR(8) + +#define U2_FLAG_MISC_CTRL_SERDES_ENABLE 8 +#define U2_FLAG_MISC_CTRL_SERDES_PRBSEN 4 +#define U2_FLAG_MISC_CTRL_SERDES_LOOPEN 2 +#define U2_FLAG_MISC_CTRL_SERDES_RXEN 1 + +#define U2_FLAG_MISC_CTRL_ADC_ON 0x0F +#define U2_FLAG_MISC_CTRL_ADC_OFF 0x00 + +///////////////////////////////////////////////// +// Readback regs +//////////////////////////////////////////////// +#define U2_REG_STATUS READBACK_BASE + 4*8 +#define U2_REG_GPIO_RB READBACK_BASE + 4*9 +#define U2_REG_TIME64_HI_RB_IMM READBACK_BASE + 4*10 +#define U2_REG_TIME64_LO_RB_IMM READBACK_BASE + 4*11 +#define U2_REG_COMPAT_NUM_RB READBACK_BASE + 4*12 +#define U2_REG_IRQ_RB READBACK_BASE + 4*13 +#define U2_REG_TIME64_HI_RB_PPS READBACK_BASE + 4*14 +#define U2_REG_TIME64_LO_RB_PPS READBACK_BASE + 4*15 + +#endif /* INCLUDED_USRP2_REGS_HPP */ diff --git a/host/lib/utils/CMakeLists.txt b/host/lib/utils/CMakeLists.txt new file mode 100644 index 000000000..95105f917 --- /dev/null +++ b/host/lib/utils/CMakeLists.txt @@ -0,0 +1,142 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Setup defines for process scheduling +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring priority scheduling...") +INCLUDE(CheckCXXSourceCompiles) + +CHECK_CXX_SOURCE_COMPILES(" + #include <pthread.h> + int main(){ + struct sched_param sp; + pthread_setschedparam(pthread_self(), SCHED_RR, &sp); + return 0; + } + " HAVE_PTHREAD_SETSCHEDPARAM +) + +IF(CYGWIN) + #SCHED_RR non-operational on cygwin + SET(HAVE_PTHREAD_SETSCHEDPARAM False) +ENDIF(CYGWIN) + +CHECK_CXX_SOURCE_COMPILES(" + #include <windows.h> + int main(){ + SetThreadPriority(GetCurrentThread(), 0); + SetPriorityClass(GetCurrentProcess(), 0); + return 0; + } + " HAVE_WIN_SETTHREADPRIORITY +) + +IF(HAVE_PTHREAD_SETSCHEDPARAM) + MESSAGE(STATUS " Priority scheduling supported through pthread_setschedparam.") + SET(THREAD_PRIO_DEFS HAVE_PTHREAD_SETSCHEDPARAM) + LIBUHD_APPEND_LIBS(pthread) +ELSEIF(HAVE_WIN_SETTHREADPRIORITY) + MESSAGE(STATUS " Priority scheduling supported through windows SetThreadPriority.") + SET(THREAD_PRIO_DEFS HAVE_WIN_SETTHREADPRIORITY) +ELSE() + MESSAGE(STATUS " Priority scheduling not supported.") + SET(THREAD_PRIO_DEFS HAVE_THREAD_PRIO_DUMMY) +ENDIF() + +SET_SOURCE_FILES_PROPERTIES( + ${CMAKE_CURRENT_SOURCE_DIR}/thread_priority.cpp + PROPERTIES COMPILE_DEFINITIONS "${THREAD_PRIO_DEFS}" +) + +######################################################################## +# Setup defines for module loading +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring module loading...") +INCLUDE(CheckCXXSourceCompiles) + +SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) +CHECK_CXX_SOURCE_COMPILES(" + #include <dlfcn.h> + int main(){ + dlopen(0, 0); + return 0; + } + " HAVE_DLOPEN +) +UNSET(CMAKE_REQUIRED_LIBRARIES) + +CHECK_CXX_SOURCE_COMPILES(" + #include <windows.h> + int main(){ + LoadLibrary(0); + return 0; + } + " HAVE_LOAD_LIBRARY +) + +IF(HAVE_DLOPEN) + MESSAGE(STATUS " Module loading supported through dlopen.") + SET(LOAD_MODULES_DEFS HAVE_DLOPEN) + LIBUHD_APPEND_LIBS(${CMAKE_DL_LIBS}) +ELSEIF(HAVE_LOAD_LIBRARY) + MESSAGE(STATUS " Module loading supported through LoadLibrary.") + SET(LOAD_MODULES_DEFS HAVE_LOAD_LIBRARY) +ELSE() + MESSAGE(STATUS " Module loading not supported.") + SET(LOAD_MODULES_DEFS HAVE_LOAD_MODULES_DUMMY) +ENDIF() + +SET_SOURCE_FILES_PROPERTIES( + ${CMAKE_CURRENT_SOURCE_DIR}/load_modules.cpp + PROPERTIES COMPILE_DEFINITIONS "${LOAD_MODULES_DEFS}" +) + +######################################################################## +# Define UHD_PKG_DATA_PATH for paths.cpp +######################################################################## +FILE(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${PKG_DATA_DIR} UHD_PKG_DATA_PATH) +STRING(REPLACE "\\" "\\\\" UHD_PKG_DATA_PATH ${UHD_PKG_DATA_PATH}) +MESSAGE(STATUS "Full package data directory: ${UHD_PKG_DATA_PATH}") + +SET_SOURCE_FILES_PROPERTIES( + ${CMAKE_CURRENT_SOURCE_DIR}/paths.cpp + PROPERTIES COMPILE_DEFINITIONS + "UHD_PKG_DATA_PATH=\"${UHD_PKG_DATA_PATH}\"" +) + +######################################################################## +# Append sources +######################################################################## +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/csv.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/gain_group.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/images.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/load_modules.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/log.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/msg.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/paths.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/static.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tasks.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/thread_priority.cpp +) diff --git a/host/lib/utils/csv.cpp b/host/lib/utils/csv.cpp new file mode 100644 index 000000000..2ffa70196 --- /dev/null +++ b/host/lib/utils/csv.cpp @@ -0,0 +1,52 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/csv.hpp> +#include <boost/foreach.hpp> + +using namespace uhd; + +csv::rows_type csv::to_rows(std::istream &input){ + csv::rows_type rows; + std::string line; + //for each line in the input stream + while (std::getline(input, line)){ + csv::row_type row(1, ""); + bool in_quote = false; + char last_ch, next_ch = ' '; + //for each character in the line + BOOST_FOREACH(char ch, line){ + last_ch = next_ch; + next_ch = ch; + //catch a quote character and change the state + //we handle double quotes by checking last_ch + if (ch == '"'){ + in_quote = not in_quote; + if (last_ch != '"') continue; + } + //a comma not inside quotes is a column delimiter + if (not in_quote and ch == ','){ + row.push_back(""); + continue; + } + //if we got here we record the character + row.back() += ch; + } + rows.push_back(row); + } + return rows; +} diff --git a/host/lib/utils/gain_group.cpp b/host/lib/utils/gain_group.cpp new file mode 100644 index 000000000..85f4977a6 --- /dev/null +++ b/host/lib/utils/gain_group.cpp @@ -0,0 +1,186 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/gain_group.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <algorithm> +#include <vector> + +using namespace uhd; + +static const bool verbose = false; + +static bool compare_by_step_size( + const size_t &rhs, const size_t &lhs, std::vector<gain_fcns_t> &fcns +){ + return fcns.at(rhs).get_range().step() > fcns.at(lhs).get_range().step(); +} + +/*! + * Get a multiple of step with the following relation: + * result = step*floor(num/step) + * + * Due to small doubleing-point inaccuracies: + * num = n*step + e, where e is a small inaccuracy. + * When e is negative, floor would yeild (n-1)*step, + * despite that n*step is really the desired result. + * This function is designed to mitigate that issue. + * + * \param num the number to approximate + * \param step the step size to round with + * \param e the small inaccuracy to account for + * \return a multiple of step approximating num + */ +template <typename T> static T floor_step(T num, T step, T e = T(0.001)){ + return step*int(num/step + e); +} + +/*********************************************************************** + * gain group implementation + **********************************************************************/ +class gain_group_impl : public gain_group{ +public: + gain_group_impl(void){ + /*NOP*/ + } + + gain_range_t get_range(const std::string &name){ + if (not name.empty()) return _name_to_fcns[name].get_range(); + + double overall_min = 0, overall_max = 0, overall_step = 0; + BOOST_FOREACH(const gain_fcns_t &fcns, get_all_fcns()){ + const gain_range_t range = fcns.get_range(); + overall_min += range.start(); + overall_max += range.stop(); + //the overall step is the min (zero is invalid, first run) + if (overall_step == 0) overall_step = range.step(); + overall_step = std::min(overall_step, range.step()); + } + return gain_range_t(overall_min, overall_max, overall_step); + } + + double get_value(const std::string &name){ + if (not name.empty()) return _name_to_fcns[name].get_value(); + + double overall_gain = 0; + BOOST_FOREACH(const gain_fcns_t &fcns, get_all_fcns()){ + overall_gain += fcns.get_value(); + } + return overall_gain; + } + + void set_value(double gain, const std::string &name){ + if (not name.empty()) return _name_to_fcns[name].set_value(gain); + + std::vector<gain_fcns_t> all_fcns = get_all_fcns(); + if (all_fcns.size() == 0) return; //nothing to set! + + //get the max step size among the gains + double max_step = 0; + BOOST_FOREACH(const gain_fcns_t &fcns, all_fcns){ + max_step = std::max(max_step, fcns.get_range().step()); + } + + //create gain bucket to distribute power + std::vector<double> gain_bucket; + + //distribute power according to priority (round to max step) + double gain_left_to_distribute = gain; + BOOST_FOREACH(const gain_fcns_t &fcns, all_fcns){ + const gain_range_t range = fcns.get_range(); + gain_bucket.push_back(floor_step(uhd::clip( + gain_left_to_distribute, range.start(), range.stop() + ), max_step)); + gain_left_to_distribute -= gain_bucket.back(); + } + + //get a list of indexes sorted by step size large to small + std::vector<size_t> indexes_step_size_dec; + for (size_t i = 0; i < all_fcns.size(); i++){ + indexes_step_size_dec.push_back(i); + } + std::sort( + indexes_step_size_dec.begin(), indexes_step_size_dec.end(), + boost::bind(&compare_by_step_size, _1, _2, all_fcns) + ); + UHD_ASSERT_THROW( + all_fcns.at(indexes_step_size_dec.front()).get_range().step() >= + all_fcns.at(indexes_step_size_dec.back()).get_range().step() + ); + + //distribute the remainder (less than max step) + //fill in the largest step sizes first that are less than the remainder + BOOST_FOREACH(size_t i, indexes_step_size_dec){ + const gain_range_t range = all_fcns.at(i).get_range(); + double additional_gain = floor_step(uhd::clip( + gain_bucket.at(i) + gain_left_to_distribute, range.start(), range.stop() + ), range.step()) - gain_bucket.at(i); + gain_bucket.at(i) += additional_gain; + gain_left_to_distribute -= additional_gain; + } + UHD_LOGV(often) << "gain_left_to_distribute " << gain_left_to_distribute << std::endl; + + //now write the bucket out to the individual gain values + for (size_t i = 0; i < gain_bucket.size(); i++){ + UHD_LOGV(often) << i << ": " << gain_bucket.at(i) << std::endl; + all_fcns.at(i).set_value(gain_bucket.at(i)); + } + } + + const std::vector<std::string> get_names(void){ + return _name_to_fcns.keys(); + } + + void register_fcns( + const std::string &name, + const gain_fcns_t &gain_fcns, + size_t priority + ){ + if (name.empty() or _name_to_fcns.has_key(name)){ + //ensure the name name is unique and non-empty + return register_fcns(name + "_", gain_fcns, priority); + } + _registry[priority].push_back(gain_fcns); + _name_to_fcns[name] = gain_fcns; + } + +private: + //! get the gain function sets in order (highest priority first) + std::vector<gain_fcns_t> get_all_fcns(void){ + std::vector<gain_fcns_t> all_fcns; + BOOST_FOREACH(size_t key, uhd::sorted(_registry.keys())){ + const std::vector<gain_fcns_t> &fcns = _registry[key]; + all_fcns.insert(all_fcns.begin(), fcns.begin(), fcns.end()); + } + return all_fcns; + } + + uhd::dict<size_t, std::vector<gain_fcns_t> > _registry; + uhd::dict<std::string, gain_fcns_t> _name_to_fcns; +}; + +/*********************************************************************** + * gain group factory function + **********************************************************************/ +gain_group::sptr gain_group::make(void){ + return sptr(new gain_group_impl()); +} diff --git a/host/lib/utils/images.cpp b/host/lib/utils/images.cpp new file mode 100644 index 000000000..a124cc208 --- /dev/null +++ b/host/lib/utils/images.cpp @@ -0,0 +1,40 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/images.hpp> +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> +#include <boost/filesystem.hpp> +#include <vector> + +namespace fs = boost::filesystem; + +std::vector<fs::path> get_image_paths(void); //defined in paths.cpp + +/*********************************************************************** + * Find a image in the image paths + **********************************************************************/ +std::string uhd::find_image_path(const std::string &image_name){ + if (fs::exists(image_name)){ + return fs::system_complete(image_name).string(); + } + BOOST_FOREACH(const fs::path &path, get_image_paths()){ + fs::path image_path = path / image_name; + if (fs::exists(image_path)) return image_path.string(); + } + throw uhd::io_error("Could not find path for image: " + image_name); +} diff --git a/host/lib/utils/load_modules.cpp b/host/lib/utils/load_modules.cpp new file mode 100644 index 000000000..bee0d5304 --- /dev/null +++ b/host/lib/utils/load_modules.cpp @@ -0,0 +1,109 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/static.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/filesystem.hpp> +#include <iostream> +#include <string> +#include <vector> + +namespace fs = boost::filesystem; + +/*********************************************************************** + * Module Load Function + **********************************************************************/ +#ifdef HAVE_DLOPEN +#include <dlfcn.h> +static void load_module(const std::string &file_name){ + if (dlopen(file_name.c_str(), RTLD_LAZY) == NULL){ + throw uhd::os_error(str( + boost::format("dlopen failed to load \"%s\"") % file_name + )); + } +} +#endif /* HAVE_DLOPEN */ + + +#ifdef HAVE_LOAD_LIBRARY +#include <windows.h> +static void load_module(const std::string &file_name){ + if (LoadLibrary(file_name.c_str()) == NULL){ + throw uhd::os_error(str( + boost::format("LoadLibrary failed to load \"%s\"") % file_name + )); + } +} +#endif /* HAVE_LOAD_LIBRARY */ + + +#ifdef HAVE_LOAD_MODULES_DUMMY +static void load_module(const std::string &file_name){ + throw uhd::not_implemented_error(str( + boost::format("Module loading not supported: Cannot load \"%s\"") % file_name + )); +} +#endif /* HAVE_LOAD_MODULES_DUMMY */ + +/*********************************************************************** + * Load Modules + **********************************************************************/ +/*! + * Load all modules in a given path. + * This will recurse into sub-directories. + * Does not throw, prints to std error. + * \param path the filesystem path + */ +static void load_module_path(const fs::path &path){ + if (not fs::exists(path)){ + //std::cerr << boost::format("Module path \"%s\" not found.") % path.string() << std::endl; + return; + } + + //try to load the files in this path + if (fs::is_directory(path)){ + for( + fs::directory_iterator dir_itr(path); + dir_itr != fs::directory_iterator(); + ++dir_itr + ){ + load_module_path(dir_itr->path()); + } + return; + } + + //its not a directory, try to load it + try{ + load_module(path.string()); + } + catch(const std::exception &err){ + std::cerr << boost::format("Error: %s") % err.what() << std::endl; + } +} + +std::vector<fs::path> get_module_paths(void); //defined in paths.cpp + +/*! + * Load all the modules given in the module paths. + */ +UHD_STATIC_BLOCK(load_modules){ + BOOST_FOREACH(const fs::path &path, get_module_paths()){ + load_module_path(path); + } +} diff --git a/host/lib/utils/log.cpp b/host/lib/utils/log.cpp new file mode 100644 index 000000000..31d11796e --- /dev/null +++ b/host/lib/utils/log.cpp @@ -0,0 +1,221 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/static.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#ifdef BOOST_MSVC +//whoops! https://svn.boost.org/trac/boost/ticket/5287 +//enjoy this useless dummy class instead +namespace boost{ namespace interprocess{ + struct file_lock{ + file_lock(const char * = NULL){} + void lock(void){} + void unlock(void){} + }; +}} //namespace +#else +#include <boost/interprocess/sync/file_lock.hpp> +#endif +#ifdef BOOST_MSVC +#define USE_GET_TEMP_PATH +#include <Windows.h> //GetTempPath +#endif +#include <stdio.h> //P_tmpdir +#include <cstdlib> //getenv +#include <fstream> +#include <sstream> +#include <cctype> + +namespace fs = boost::filesystem; +namespace pt = boost::posix_time; +namespace ip = boost::interprocess; + +/*********************************************************************** + * Helper function to get the system's temporary path + **********************************************************************/ +static fs::path get_temp_path(void){ + const char *tmp_path = NULL; + + //try the official uhd temp path environment variable + tmp_path = std::getenv("UHD_TEMP_PATH"); + if (tmp_path != NULL) return tmp_path; + + //try the windows function if available + #ifdef USE_GET_TEMP_PATH + char lpBuffer[2048]; + if (GetTempPath(sizeof(lpBuffer), lpBuffer)) return lpBuffer; + #endif + + //try windows environment variables + tmp_path = std::getenv("TMP"); + if (tmp_path != NULL) return tmp_path; + + tmp_path = std::getenv("TEMP"); + if (tmp_path != NULL) return tmp_path; + + //try the stdio define if available + #ifdef P_tmpdir + return P_tmpdir; + #endif + + //try unix environment variables + tmp_path = std::getenv("TMPDIR"); + if (tmp_path != NULL) return tmp_path; + + //give up and use the unix default + return "/tmp"; +} + +/*********************************************************************** + * Global resources for the logger + **********************************************************************/ +class log_resource_type{ +public: + uhd::_log::verbosity_t level; + + log_resource_type(void){ + + //file lock pointer must be null + _file_lock = NULL; + + //set the default log level + level = uhd::_log::never; + + //allow override from macro definition + #ifdef UHD_LOG_LEVEL + _set_log_level(BOOST_STRINGIZE(UHD_LOG_LEVEL)); + #endif + + //allow override from environment variable + const char * log_level_env = std::getenv("UHD_LOG_LEVEL"); + if (log_level_env != NULL) _set_log_level(log_level_env); + } + + ~log_resource_type(void){ + boost::mutex::scoped_lock lock(_mutex); + _file_stream.close(); + if (_file_lock != NULL) delete _file_lock; + } + + void log_to_file(const std::string &log_msg){ + boost::mutex::scoped_lock lock(_mutex); + if (_file_lock == NULL){ + const std::string log_path = (get_temp_path() / "uhd.log").string(); + _file_stream.open(log_path.c_str(), std::fstream::out | std::fstream::app); + _file_lock = new ip::file_lock(log_path.c_str()); + } + _file_lock->lock(); + _file_stream << log_msg << std::flush; + _file_lock->unlock(); + } + +private: + //! set the log level from a string that is either a digit or an enum name + void _set_log_level(const std::string &log_level_str){ + const uhd::_log::verbosity_t log_level_num = uhd::_log::verbosity_t(log_level_str[0]-'0'); + if (std::isdigit(log_level_str[0]) and log_level_num >= uhd::_log::always and log_level_num <= uhd::_log::never){ + this->level = log_level_num; + return; + } + #define if_lls_equal(name) else if(log_level_str == #name) this->level = uhd::_log::name + if_lls_equal(always); + if_lls_equal(often); + if_lls_equal(regularly); + if_lls_equal(rarely); + if_lls_equal(very_rarely); + if_lls_equal(never); + } + + //file stream and lock: + std::ofstream _file_stream; + ip::file_lock *_file_lock; + boost::mutex _mutex; +}; + +UHD_SINGLETON_FCN(log_resource_type, log_rs); + +/*********************************************************************** + * The logger object implementation + **********************************************************************/ +//! get the relative file path from the host directory +static std::string get_rel_file_path(const fs::path &file){ + fs::path abs_path = file.branch_path(); + fs::path rel_path = file.leaf(); + while (not abs_path.empty() and abs_path.leaf() != "host"){ + rel_path = abs_path.leaf() / rel_path; + abs_path = abs_path.branch_path(); + } + return rel_path.string(); +} + +struct uhd::_log::log::impl{ + std::ostringstream ss; + verbosity_t verbosity; +}; + +uhd::_log::log::log( + const verbosity_t verbosity, + const std::string &file, + const unsigned int line, + const std::string &function +){ + _impl = UHD_PIMPL_MAKE(impl, ()); + _impl->verbosity = verbosity; + const std::string time = pt::to_simple_string(pt::microsec_clock::local_time()); + const std::string header1 = str(boost::format("-- %s - level %d") % time % int(verbosity)); + const std::string header2 = str(boost::format("-- %s") % function).substr(0, 80); + const std::string header3 = str(boost::format("-- %s:%u") % get_rel_file_path(file) % line); + const std::string border = std::string(std::max(std::max(header1.size(), header2.size()), header3.size()), '-'); + _impl->ss + << std::endl + << border << std::endl + << header1 << std::endl + << header2 << std::endl + << header3 << std::endl + << border << std::endl + ; +} + +uhd::_log::log::~log(void){ + if (_impl->verbosity < log_rs().level) return; + _impl->ss << std::endl; + try{ + log_rs().log_to_file(_impl->ss.str()); + } + catch(const std::exception &e){ + /*! + * Critical behavior below. + * The following steps must happen in order to avoid a lock-up condition. + * This is because the message facility will call into the logging facility. + * Therefore we must disable the logger (level = never) before messaging. + */ + log_rs().level = never; + UHD_MSG(error) + << "Logging failed: " << e.what() << std::endl + << "Logging has been disabled for this process" << std::endl + ; + } +} + +std::ostream & uhd::_log::log::operator()(void){ + return _impl->ss; +} diff --git a/host/lib/utils/msg.cpp b/host/lib/utils/msg.cpp new file mode 100644 index 000000000..de98ada64 --- /dev/null +++ b/host/lib/utils/msg.cpp @@ -0,0 +1,128 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/foreach.hpp> +#include <boost/tokenizer.hpp> +#include <sstream> +#include <iostream> + +/*********************************************************************** + * Helper functions + **********************************************************************/ +#define tokenizer(inp, sep) \ + boost::tokenizer<boost::char_separator<char> > \ + (inp, boost::char_separator<char>(sep)) + +static void msg_to_cout(const std::string &msg){ + std::stringstream ss; + + static bool just_had_a_newline = true; + BOOST_FOREACH(char ch, msg){ + if (just_had_a_newline){ + just_had_a_newline = false; + ss << "-- "; + } + if (ch == '\n'){ + just_had_a_newline = true; + } + ss << ch; + } + + std::cout << ss.str() << std::flush; +} + +static void msg_to_cerr(const std::string &title, const std::string &msg){ + std::stringstream ss; + + ss << std::endl << title << ":" << std::endl; + BOOST_FOREACH(const std::string &line, tokenizer(msg, "\n")){ + ss << " " << line << std::endl; + } + + std::cerr << ss.str() << std::flush; +} + +/*********************************************************************** + * Global resources for the messenger + **********************************************************************/ +struct msg_resource_type{ + boost::mutex mutex; + uhd::msg::handler_t handler; +}; + +UHD_SINGLETON_FCN(msg_resource_type, msg_rs); + +/*********************************************************************** + * Setup the message handlers + **********************************************************************/ +void uhd::msg::register_handler(const handler_t &handler){ + boost::mutex::scoped_lock lock(msg_rs().mutex); + msg_rs().handler = handler; +} + +static void default_msg_handler(uhd::msg::type_t type, const std::string &msg){ + switch(type){ + case uhd::msg::fastpath: + std::cerr << msg << std::flush; + break; + + case uhd::msg::status: + msg_to_cout(msg); + UHD_LOG << "Status message" << std::endl << msg; + break; + + case uhd::msg::warning: + msg_to_cerr("UHD Warning", msg); + UHD_LOG << "Warning message" << std::endl << msg; + break; + + case uhd::msg::error: + msg_to_cerr("UHD Error", msg); + UHD_LOG << "Error message" << std::endl << msg; + break; + } +} + +UHD_STATIC_BLOCK(msg_register_default_handler){ + uhd::msg::register_handler(&default_msg_handler); +} + +/*********************************************************************** + * The message object implementation + **********************************************************************/ +struct uhd::msg::_msg::impl{ + std::ostringstream ss; + type_t type; +}; + +uhd::msg::_msg::_msg(const type_t type){ + _impl = UHD_PIMPL_MAKE(impl, ()); + _impl->type = type; +} + +uhd::msg::_msg::~_msg(void){ + boost::mutex::scoped_lock lock(msg_rs().mutex); + msg_rs().handler(_impl->type, _impl->ss.str()); +} + +std::ostream & uhd::msg::_msg::operator()(void){ + return _impl->ss; +} diff --git a/host/lib/utils/paths.cpp b/host/lib/utils/paths.cpp new file mode 100644 index 000000000..4fc877d5d --- /dev/null +++ b/host/lib/utils/paths.cpp @@ -0,0 +1,108 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/tokenizer.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <cstdlib> +#include <string> +#include <vector> +#include <cstdlib> //getenv +#include <cstdio> //P_tmpdir + +namespace fs = boost::filesystem; + +/*********************************************************************** + * Determine the paths separator + **********************************************************************/ +#ifdef UHD_PLATFORM_WIN32 + static const std::string env_path_sep = ";"; +#else + static const std::string env_path_sep = ":"; +#endif /*UHD_PLATFORM_WIN32*/ + +#define path_tokenizer(inp) \ + boost::tokenizer<boost::char_separator<char> > \ + (inp, boost::char_separator<char>(env_path_sep.c_str())) + +/*********************************************************************** + * Get a list of paths for an environment variable + **********************************************************************/ +static std::string get_env_var(const std::string &var_name, const std::string &def_val = ""){ + const char *var_value_ptr = std::getenv(var_name.c_str()); + return (var_value_ptr == NULL)? def_val : var_value_ptr; +} + +static std::vector<fs::path> get_env_paths(const std::string &var_name){ + + std::string var_value = get_env_var(var_name); + + //convert to filesystem path, filter blank paths + std::vector<fs::path> paths; + if (var_value.empty()) return paths; //FIXME boost tokenizer throws w/ blank strings on some platforms + BOOST_FOREACH(const std::string &path_string, path_tokenizer(var_value)){ + if (path_string.empty()) continue; + paths.push_back(fs::system_complete(path_string)); + } + return paths; +} + +/*********************************************************************** + * Get a list of special purpose paths + **********************************************************************/ +static fs::path get_uhd_pkg_data_path(void){ + return fs::path(get_env_var("UHD_PKG_DATA_PATH", UHD_PKG_DATA_PATH)); +} + +std::vector<fs::path> get_image_paths(void){ + std::vector<fs::path> paths = get_env_paths("UHD_IMAGE_PATH"); + paths.push_back(get_uhd_pkg_data_path() / "images"); + return paths; +} + +std::vector<fs::path> get_module_paths(void){ + std::vector<fs::path> paths = get_env_paths("UHD_MODULE_PATH"); + paths.push_back(get_uhd_pkg_data_path() / "modules"); + return paths; +} + +/*********************************************************************** + * Implement the functions in paths.hpp + **********************************************************************/ +std::string uhd::get_tmp_path(void){ + const char *tmp_path = std::getenv("TMP"); + if (tmp_path != NULL) return tmp_path; + + #ifdef P_tmpdir + if (P_tmpdir != NULL) return P_tmpdir; + #endif + + return "/tmp"; +} + +std::string uhd::get_app_path(void){ + const char *appdata_path = std::getenv("APPDATA"); + if (appdata_path != NULL) return appdata_path; + + const char *home_path = std::getenv("HOME"); + if (home_path != NULL) return home_path; + + return uhd::get_tmp_path(); +} diff --git a/host/lib/utils/static.cpp b/host/lib/utils/static.cpp new file mode 100644 index 000000000..a0dea3372 --- /dev/null +++ b/host/lib/utils/static.cpp @@ -0,0 +1,32 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/static.hpp> +#include <stdexcept> +#include <iostream> +_uhd_static_fixture::_uhd_static_fixture(void (*fcn)(void), const char *name){ + try{ + fcn(); + } + catch(const std::exception &e){ + std::cerr << "Exception in static block " << name << std::endl; + std::cerr << " " << e.what() << std::endl; + } + catch(...){ + std::cerr << "Exception in static block " << name << std::endl; + } +} diff --git a/host/lib/utils/tasks.cpp b/host/lib/utils/tasks.cpp new file mode 100644 index 000000000..1f735de06 --- /dev/null +++ b/host/lib/utils/tasks.cpp @@ -0,0 +1,82 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/barrier.hpp> +#include <exception> +#include <iostream> + +using namespace uhd; + +class task_impl : public task{ +public: + + task_impl(const task_fcn_type &task_fcn): + _spawn_barrier(2) + { + _thread_group.create_thread(boost::bind(&task_impl::task_loop, this, task_fcn)); + _spawn_barrier.wait(); + } + + ~task_impl(void){ + _running = false; + _thread_group.interrupt_all(); + _thread_group.join_all(); + } + +private: + + void task_loop(const task_fcn_type &task_fcn){ + _running = true; + _spawn_barrier.wait(); + + try{ + while (_running){ + task_fcn(); + } + } + catch(const boost::thread_interrupted &){ + //this is an ok way to exit the task loop + } + catch(const std::exception &e){ + do_error_msg(e.what()); + } + catch(...){ + //FIXME + //Unfortunately, this is also an ok way to end a task, + //because on some systems boost throws uncatchables. + } + } + + void do_error_msg(const std::string &msg){ + UHD_MSG(error) + << "An unexpected exception was caught in a task loop." << std::endl + << "The task loop will now exit, things may not work." << std::endl + << msg << std::endl + ; + } + + boost::thread_group _thread_group; + boost::barrier _spawn_barrier; + bool _running; +}; + +task::sptr task::make(const task_fcn_type &task_fcn){ + return task::sptr(new task_impl(task_fcn)); +} diff --git a/host/lib/utils/thread_priority.cpp b/host/lib/utils/thread_priority.cpp new file mode 100644 index 000000000..7c3faa37a --- /dev/null +++ b/host/lib/utils/thread_priority.cpp @@ -0,0 +1,110 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <iostream> + +bool uhd::set_thread_priority_safe(float priority, bool realtime){ + try{ + set_thread_priority(priority, realtime); + return true; + }catch(const std::exception &e){ + UHD_MSG(warning) << boost::format( + "Unable to set the thread priority. Performance may be negatively affected.\n" + "Please see the general application notes in the manual for instructions.\n" + "%s\n" + ) % e.what(); + return false; + } +} + +static void check_priority_range(float priority){ + if (priority > +1.0 or priority < -1.0) + throw uhd::value_error("priority out of range [-1.0, +1.0]"); +} + +/*********************************************************************** + * Pthread API to set priority + **********************************************************************/ +#ifdef HAVE_PTHREAD_SETSCHEDPARAM + #include <pthread.h> + + void uhd::set_thread_priority(float priority, bool realtime){ + check_priority_range(priority); + + //when realtime is not enabled, use sched other + int policy = (realtime)? SCHED_RR : SCHED_OTHER; + + //we cannot have below normal priority, set to zero + if (priority < 0) priority = 0; + + //get the priority bounds for the selected policy + int min_pri = sched_get_priority_min(policy); + int max_pri = sched_get_priority_max(policy); + if (min_pri == -1 or max_pri == -1) throw uhd::os_error("error in sched_get_priority_min/max"); + + //set the new priority and policy + sched_param sp; + sp.sched_priority = int(priority*(max_pri - min_pri)) + min_pri; + int ret = pthread_setschedparam(pthread_self(), policy, &sp); + if (ret != 0) throw uhd::os_error("error in pthread_setschedparam"); + } +#endif /* HAVE_PTHREAD_SETSCHEDPARAM */ + +/*********************************************************************** + * Windows API to set priority + **********************************************************************/ +#ifdef HAVE_WIN_SETTHREADPRIORITY + #include <windows.h> + + void uhd::set_thread_priority(float priority, bool realtime){ + check_priority_range(priority); + + /* + * Process wide priority is no longer set. + * This is the responsibility of the application. + //set the priority class on the process + int pri_class = (realtime)? REALTIME_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS; + if (SetPriorityClass(GetCurrentProcess(), pri_class) == 0) + throw uhd::os_error("error in SetPriorityClass"); + */ + + //scale the priority value to the constants + int priorities[] = { + THREAD_PRIORITY_IDLE, THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_NORMAL, + THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST, THREAD_PRIORITY_TIME_CRITICAL + }; + size_t pri_index = size_t((priority+1.0)*6/2.0); // -1 -> 0, +1 -> 6 + + //set the thread priority on the thread + if (SetThreadPriority(GetCurrentThread(), priorities[pri_index]) == 0) + throw uhd::os_error("error in SetThreadPriority"); + } +#endif /* HAVE_WIN_SETTHREADPRIORITY */ + +/*********************************************************************** + * Unimplemented API to set priority + **********************************************************************/ +#ifdef HAVE_THREAD_PRIO_DUMMY + void uhd::set_thread_priority(float, bool){ + throw uhd::not_implemented_error("set thread priority not implemented"); + } + +#endif /* HAVE_THREAD_PRIO_DUMMY */ diff --git a/host/lib/version.cpp b/host/lib/version.cpp new file mode 100644 index 000000000..42880b3c3 --- /dev/null +++ b/host/lib/version.cpp @@ -0,0 +1,37 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/version.hpp> +#include <uhd/utils/static.hpp> +#include <boost/version.hpp> +#include <iostream> + +#ifndef UHD_DONT_PRINT_SYSTEM_INFO +UHD_STATIC_BLOCK(print_system_info){ + std::cout + << BOOST_PLATFORM << "; " + << BOOST_COMPILER << "; " + << "Boost_" << BOOST_VERSION << "; " + << "UHD_" << uhd::get_version_string() + << std::endl << std::endl + ; +} +#endif + +std::string uhd::get_version_string(void){ + return "@UHD_VERSION@"; +} diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt new file mode 100644 index 000000000..67e99941b --- /dev/null +++ b/host/tests/CMakeLists.txt @@ -0,0 +1,57 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# unit test suite +######################################################################## +SET(test_sources + addr_test.cpp + buffer_test.cpp + byteswap_test.cpp + convert_test.cpp + dict_test.cpp + error_test.cpp + gain_group_test.cpp + msg_test.cpp + property_test.cpp + ranges_test.cpp + sph_recv_test.cpp + sph_send_test.cpp + subdev_spec_test.cpp + time_spec_test.cpp + vrt_test.cpp +) + +#turn each test cpp file into an executable with an int main() function +ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN) + +#for each source: build an executable, register it as a test, and install +FOREACH(test_source ${test_sources}) + GET_FILENAME_COMPONENT(test_name ${test_source} NAME_WE) + ADD_EXECUTABLE(${test_name} ${test_source}) + TARGET_LINK_LIBRARIES(${test_name} uhd) + ADD_TEST(${test_name} ${test_name}) + INSTALL(TARGETS ${test_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) +ENDFOREACH(test_source) + +######################################################################## +# demo of a loadable module +######################################################################## +IF(MSVC OR APPLE OR LINUX) + ADD_LIBRARY(module_test MODULE module_test.cpp) + TARGET_LINK_LIBRARIES(module_test uhd) +ENDIF() diff --git a/host/tests/addr_test.cpp b/host/tests/addr_test.cpp new file mode 100644 index 000000000..cea2f224c --- /dev/null +++ b/host/tests/addr_test.cpp @@ -0,0 +1,81 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/types/mac_addr.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <algorithm> +#include <iostream> + +BOOST_AUTO_TEST_CASE(test_mac_addr){ + std::cout << "Testing mac addr..." << std::endl; + const std::string mac_addr_str("00:01:23:45:67:89"); + uhd::mac_addr_t mac_addr = uhd::mac_addr_t::from_string(mac_addr_str); + std::cout << "Input: " << mac_addr_str << std::endl; + std::cout << "Output: " << mac_addr.to_string() << std::endl; + BOOST_CHECK_EQUAL(mac_addr_str, mac_addr.to_string()); +} + +BOOST_AUTO_TEST_CASE(test_device_addr){ + std::cout << "Testing device addr..." << std::endl; + + //load the device address with something + uhd::device_addr_t dev_addr; + dev_addr["key1"] = "val1"; + dev_addr["key1"] = "val1"; + dev_addr["key3"] = ""; + + //convert to and from args string + std::cout << "Pretty Print: " << std::endl << dev_addr.to_pp_string(); + std::string args_str = dev_addr.to_string(); + std::cout << "Args String: " << args_str << std::endl; + uhd::device_addr_t new_dev_addr(args_str); + + //they should be the same size + BOOST_REQUIRE_EQUAL(dev_addr.size(), new_dev_addr.size()); + + //the keys should match + std::vector<std::string> old_dev_addr_keys = dev_addr.keys(); + std::vector<std::string> new_dev_addr_keys = new_dev_addr.keys(); + BOOST_CHECK_EQUAL_COLLECTIONS( + old_dev_addr_keys.begin(), old_dev_addr_keys.end(), + new_dev_addr_keys.begin(), new_dev_addr_keys.end() + ); + + //the vals should match + std::vector<std::string> old_dev_addr_vals = dev_addr.vals(); + std::vector<std::string> new_dev_addr_vals = new_dev_addr.vals(); + BOOST_CHECK_EQUAL_COLLECTIONS( + old_dev_addr_vals.begin(), old_dev_addr_vals.end(), + new_dev_addr_vals.begin(), new_dev_addr_vals.end() + ); +} + +BOOST_AUTO_TEST_CASE(test_dboard_id){ + std::cout << "Testing dboard id..." << std::endl; + + using namespace uhd::usrp; + + BOOST_CHECK(dboard_id_t() == dboard_id_t::none()); + BOOST_CHECK_EQUAL(dboard_id_t().to_uint16(), dboard_id_t::none().to_uint16()); + BOOST_CHECK_EQUAL(dboard_id_t::from_string("0x1234").to_uint16(), 0x1234); + BOOST_CHECK_EQUAL(dboard_id_t::from_string("1234").to_uint16(), 1234); + std::cout << "Pretty Print: " << std::endl << dboard_id_t::none().to_pp_string(); +} diff --git a/host/tests/buffer_test.cpp b/host/tests/buffer_test.cpp new file mode 100644 index 000000000..23b52a9bf --- /dev/null +++ b/host/tests/buffer_test.cpp @@ -0,0 +1,64 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/assign/list_of.hpp> + +using namespace boost::assign; +using namespace uhd::transport; + +static const double timeout = 0.01/*secs*/; + +BOOST_AUTO_TEST_CASE(test_bounded_buffer_with_timed_wait){ + bounded_buffer<int> bb(3); + + //push elements, check for timeout + BOOST_CHECK(bb.push_with_timed_wait(0, timeout)); + BOOST_CHECK(bb.push_with_timed_wait(1, timeout)); + BOOST_CHECK(bb.push_with_timed_wait(2, timeout)); + BOOST_CHECK(not bb.push_with_timed_wait(3, timeout)); + + int val; + //pop elements, check for timeout and check values + BOOST_CHECK(bb.pop_with_timed_wait(val, timeout)); + BOOST_CHECK_EQUAL(val, 0); + BOOST_CHECK(bb.pop_with_timed_wait(val, timeout)); + BOOST_CHECK_EQUAL(val, 1); + BOOST_CHECK(bb.pop_with_timed_wait(val, timeout)); + BOOST_CHECK_EQUAL(val, 2); + BOOST_CHECK(not bb.pop_with_timed_wait(val, timeout)); +} + +BOOST_AUTO_TEST_CASE(test_bounded_buffer_with_pop_on_full){ + bounded_buffer<int> bb(3); + + //push elements, check for timeout + BOOST_CHECK(bb.push_with_pop_on_full(0)); + BOOST_CHECK(bb.push_with_pop_on_full(1)); + BOOST_CHECK(bb.push_with_pop_on_full(2)); + BOOST_CHECK(not bb.push_with_pop_on_full(3)); + + int val; + //pop elements, check for timeout and check values + BOOST_CHECK(bb.pop_with_timed_wait(val, timeout)); + BOOST_CHECK_EQUAL(val, 1); + BOOST_CHECK(bb.pop_with_timed_wait(val, timeout)); + BOOST_CHECK_EQUAL(val, 2); + BOOST_CHECK(bb.pop_with_timed_wait(val, timeout)); + BOOST_CHECK_EQUAL(val, 3); +} diff --git a/host/tests/byteswap_test.cpp b/host/tests/byteswap_test.cpp new file mode 100644 index 000000000..7d94bbfba --- /dev/null +++ b/host/tests/byteswap_test.cpp @@ -0,0 +1,39 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/utils/byteswap.hpp> + +BOOST_AUTO_TEST_CASE(test_byteswap16){ + boost::uint16_t x = 0x0123; + boost::uint16_t y = 0x2301; + BOOST_CHECK_EQUAL(uhd::byteswap(x), y); +} + +BOOST_AUTO_TEST_CASE(test_byteswap32){ + boost::uint32_t x = 0x01234567; + boost::uint32_t y = 0x67452301; + BOOST_CHECK_EQUAL(uhd::byteswap(x), y); +} + +BOOST_AUTO_TEST_CASE(test_byteswap64){ + //split up 64 bit constants to avoid long-long compiler warnings + boost::uint64_t x = 0x01234567 | (boost::uint64_t(0x89abcdef) << 32); + boost::uint64_t y = 0xefcdab89 | (boost::uint64_t(0x67452301) << 32); + BOOST_CHECK_EQUAL(uhd::byteswap(x), y); +} + diff --git a/host/tests/convert_test.cpp b/host/tests/convert_test.cpp new file mode 100644 index 000000000..6b0ae53a9 --- /dev/null +++ b/host/tests/convert_test.cpp @@ -0,0 +1,343 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/convert.hpp> +#include <boost/test/unit_test.hpp> +#include <boost/foreach.hpp> +#include <boost/cstdint.hpp> +#include <complex> +#include <vector> +#include <cstdlib> +#include <iostream> + +using namespace uhd; + +//typedefs for complex types +typedef std::complex<boost::int16_t> sc16_t; +typedef std::complex<float> fc32_t; +typedef std::complex<double> fc64_t; + +#define MY_CHECK_CLOSE(a, b, f) { \ + BOOST_CHECK_MESSAGE(std::abs((a)-(b)) < f, "\n\t" << #a << " (" << (a) << ") error " << #b << " (" << (b) << ")"); \ +} + +/*********************************************************************** + * Loopback runner: + * convert input buffer into intermediate buffer + * convert intermediate buffer into output buffer + **********************************************************************/ +template <typename Range> static void loopback( + size_t nsamps, + convert::id_type &in_id, + convert::id_type &out_id, + const Range &input, + Range &output +){ + //item32 is largest device type + std::vector<boost::uint32_t> interm(nsamps); + + std::vector<const void *> input0(1, &input[0]), input1(1, &interm[0]); + std::vector<void *> output0(1, &interm[0]), output1(1, &output[0]); + + //convert to intermediate type + convert::converter::sptr c0 = convert::get_converter(in_id)(); + c0->set_scalar(32767.); + c0->conv(input0, output0, nsamps); + + //convert back to host type + convert::converter::sptr c1 = convert::get_converter(out_id)(); + c1->set_scalar(1/32767.); + c1->conv(input1, output1, nsamps); +} + +/*********************************************************************** + * Test short conversion + **********************************************************************/ +static void test_convert_types_sc16( + size_t nsamps, convert::id_type &id, const int extra_div = 1 +){ + //fill the input samples + std::vector<sc16_t> input(nsamps), output(nsamps); + BOOST_FOREACH(sc16_t &in, input) in = sc16_t( + short(((std::rand()/double(RAND_MAX/2)) - 1)*32767/extra_div), + short(((std::rand()/double(RAND_MAX/2)) - 1)*32767/extra_div) + ); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_be_sc16){ + convert::id_type id; + id.input_format = "sc16"; + id.num_inputs = 1; + id.output_format = "sc16_item32_be"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_sc16(nsamps, id); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_le_sc16){ + convert::id_type id; + id.input_format = "sc16"; + id.num_inputs = 1; + id.output_format = "sc16_item32_le"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_sc16(nsamps, id); + } +} + +/*********************************************************************** + * Test float conversion + **********************************************************************/ +template <typename data_type> +static void test_convert_types_for_floats( + size_t nsamps, convert::id_type &id, const double extra_scale = 1.0 +){ + typedef typename data_type::value_type value_type; + + //fill the input samples + std::vector<data_type> input(nsamps), output(nsamps); + BOOST_FOREACH(data_type &in, input) in = data_type( + ((std::rand()/value_type(RAND_MAX/2)) - 1)*float(extra_scale), + ((std::rand()/value_type(RAND_MAX/2)) - 1)*float(extra_scale) + ); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + for (size_t i = 0; i < nsamps; i++){ + MY_CHECK_CLOSE(input[i].real(), output[i].real(), value_type(1./32767)); + MY_CHECK_CLOSE(input[i].imag(), output[i].imag(), value_type(1./32767)); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_be_fc32){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.output_format = "sc16_item32_be"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc32_t>(nsamps, id); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_le_fc32){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.output_format = "sc16_item32_le"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc32_t>(nsamps, id); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_be_fc64){ + convert::id_type id; + id.input_format = "fc64"; + id.num_inputs = 1; + id.output_format = "sc16_item32_be"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc64_t>(nsamps, id); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_le_fc64){ + convert::id_type id; + id.input_format = "fc64"; + id.num_inputs = 1; + id.output_format = "sc16_item32_le"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc64_t>(nsamps, id); + } +} + +/*********************************************************************** + * Test float to short conversion loopback + **********************************************************************/ +BOOST_AUTO_TEST_CASE(test_convert_types_fc32_to_sc16){ + convert::id_type in_id; + in_id.input_format = "fc32"; + in_id.num_inputs = 1; + in_id.output_format = "sc16_item32_le"; + in_id.num_outputs = 1; + + convert::id_type out_id; + out_id.input_format = "sc16_item32_le"; + out_id.num_inputs = 1; + out_id.output_format = "sc16"; + out_id.num_outputs = 1; + + const size_t nsamps = 13; + std::vector<fc32_t> input(nsamps); + BOOST_FOREACH(fc32_t &in, input) in = fc32_t( + (std::rand()/float(RAND_MAX/2)) - 1, + (std::rand()/float(RAND_MAX/2)) - 1 + ); + std::vector<boost::uint32_t> interm(nsamps); + std::vector<sc16_t> output(nsamps); + + std::vector<const void *> input0(1, &input[0]), input1(1, &interm[0]); + std::vector<void *> output0(1, &interm[0]), output1(1, &output[0]); + + //convert float to intermediate + convert::converter::sptr c0 = convert::get_converter(in_id)(); + c0->set_scalar(32767.); + c0->conv(input0, output0, nsamps); + + //convert intermediate to short + convert::converter::sptr c1 = convert::get_converter(out_id)(); + c1->set_scalar(1/32767.); + c1->conv(input1, output1, nsamps); + + //test that the inputs and outputs match + for (size_t i = 0; i < nsamps; i++){ + MY_CHECK_CLOSE(input[i].real(), output[i].real()/float(32767), float(0.01)); + MY_CHECK_CLOSE(input[i].imag(), output[i].imag()/float(32767), float(0.01)); + } +} + +/*********************************************************************** + * Test short to float conversion loopback + **********************************************************************/ +BOOST_AUTO_TEST_CASE(test_convert_types_sc16_to_fc32){ + convert::id_type in_id; + in_id.input_format = "sc16"; + in_id.num_inputs = 1; + in_id.output_format = "sc16_item32_le"; + in_id.num_outputs = 1; + + convert::id_type out_id; + out_id.input_format = "sc16_item32_le"; + out_id.num_inputs = 1; + out_id.output_format = "fc32"; + out_id.num_outputs = 1; + + const size_t nsamps = 13; + std::vector<sc16_t> input(nsamps); + BOOST_FOREACH(sc16_t &in, input) in = sc16_t( + std::rand()-(RAND_MAX/2), + std::rand()-(RAND_MAX/2) + ); + std::vector<boost::uint32_t> interm(nsamps); + std::vector<fc32_t> output(nsamps); + + std::vector<const void *> input0(1, &input[0]), input1(1, &interm[0]); + std::vector<void *> output0(1, &interm[0]), output1(1, &output[0]); + + //convert short to intermediate + convert::converter::sptr c0 = convert::get_converter(in_id)(); + c0->set_scalar(32767.); + c0->conv(input0, output0, nsamps); + + //convert intermediate to float + convert::converter::sptr c1 = convert::get_converter(out_id)(); + c1->set_scalar(1/32767.); + c1->conv(input1, output1, nsamps); + + //test that the inputs and outputs match + for (size_t i = 0; i < nsamps; i++){ + MY_CHECK_CLOSE(input[i].real()/float(32767), output[i].real(), float(0.01)); + MY_CHECK_CLOSE(input[i].imag()/float(32767), output[i].imag(), float(0.01)); + } +} + +/*********************************************************************** + * Test sc8 conversions + **********************************************************************/ +BOOST_AUTO_TEST_CASE(test_convert_types_fc64_and_sc8){ + convert::id_type id; + id.input_format = "fc64"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "sc8_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc64_t>(nsamps, id, 1./256); + } + + //try various lengths to test edge cases + id.output_format = "sc8_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc64_t>(nsamps, id, 1./256); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_fc32_and_sc8){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "sc8_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc32_t>(nsamps, id, 1./256); + } + + //try various lengths to test edge cases + id.output_format = "sc8_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc32_t>(nsamps, id, 1./256); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_sc16_and_sc8){ + convert::id_type id; + id.input_format = "sc16"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "sc8_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_sc16(nsamps, id, 256); + } + + //try various lengths to test edge cases + id.output_format = "sc8_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_sc16(nsamps, id, 256); + } +} diff --git a/host/tests/dict_test.cpp b/host/tests/dict_test.cpp new file mode 100644 index 000000000..7b388d090 --- /dev/null +++ b/host/tests/dict_test.cpp @@ -0,0 +1,72 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/types/dict.hpp> +#include <boost/assign/list_of.hpp> + +BOOST_AUTO_TEST_CASE(test_dict_init){ + uhd::dict<int, int> d; + d[-1] = 3; + d[0] = 4; + d[1] = 5; + BOOST_CHECK(d.has_key(0)); + BOOST_CHECK(not d.has_key(2)); + BOOST_CHECK(d.keys()[1] == 0); + BOOST_CHECK(d.vals()[1] == 4); + BOOST_CHECK_EQUAL(d[-1], 3); +} + +BOOST_AUTO_TEST_CASE(test_dict_assign){ + uhd::dict<int, int> d = boost::assign::map_list_of + (-1, 3) + (0, 4) + (1, 5) + ; + BOOST_CHECK(d.has_key(0)); + BOOST_CHECK(not d.has_key(2)); + BOOST_CHECK(d.keys()[1] == 0); + BOOST_CHECK(d.vals()[1] == 4); + BOOST_CHECK_EQUAL(d[-1], 3); +} + +BOOST_AUTO_TEST_CASE(test_const_dict){ + const uhd::dict<int, int> d = boost::assign::map_list_of + (-1, 3) + (0, 4) + (1, 5) + ; + BOOST_CHECK(d.has_key(0)); + BOOST_CHECK(not d.has_key(2)); + BOOST_CHECK(d.keys()[1] == 0); + BOOST_CHECK(d.vals()[1] == 4); + BOOST_CHECK_EQUAL(d[-1], 3); + BOOST_CHECK_THROW(d[2], std::exception); +} + +BOOST_AUTO_TEST_CASE(test_dict_pop){ + uhd::dict<int, int> d = boost::assign::map_list_of + (-1, 3) + (0, 4) + (1, 5) + ; + BOOST_CHECK(d.has_key(0)); + BOOST_CHECK_EQUAL(d.pop(0), 4); + BOOST_CHECK(not d.has_key(0)); + BOOST_CHECK(d.keys()[0] == -1); + BOOST_CHECK(d.keys()[1] == 1); +} diff --git a/host/tests/error_test.cpp b/host/tests/error_test.cpp new file mode 100644 index 000000000..3d784b1f7 --- /dev/null +++ b/host/tests/error_test.cpp @@ -0,0 +1,93 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/assert_has.hpp> +#include <vector> +#include <iostream> + +BOOST_AUTO_TEST_CASE(test_exception_methods){ + try{ + throw uhd::assertion_error("your assertion failed: 1 != 2"); + } + catch(const uhd::exception &e){ + std::cout << "what: " << e.what() << std::endl; + std::cout << "code: " << e.code() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_assert_has){ + std::vector<int> vec; + vec.push_back(2); + vec.push_back(3); + vec.push_back(5); + + //verify the uhd::has utility + BOOST_CHECK(uhd::has(vec, 2)); + BOOST_CHECK(not uhd::has(vec, 1)); + + std::cout << "The output of the assert_has error:" << std::endl; + try{ + uhd::assert_has(vec, 1, "prime"); + } + catch(const std::exception &e){ + std::cout << e.what() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_assert_throw){ + std::cout << "The output of the assert throw error:" << std::endl; + try{ + UHD_ASSERT_THROW(2 + 2 == 5); + } + catch(const std::exception &e){ + std::cout << e.what() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_exception_dynamic){ + uhd::exception *exception_clone; + + //throw an exception and dynamically clone it + try{ + throw uhd::runtime_error("noooooo"); + } + catch(const uhd::exception &e){ + std::cout << e.what() << std::endl; + exception_clone = e.dynamic_clone(); + } + + //now we dynamically re-throw the exception + try{ + exception_clone->dynamic_throw(); + } + catch(const uhd::assertion_error &e){ + std::cout << e.what() << std::endl; + BOOST_CHECK(false); + } + catch(const uhd::runtime_error &e){ + std::cout << e.what() << std::endl; + BOOST_CHECK(true); + } + catch(const uhd::exception &e){ + std::cout << e.what() << std::endl; + BOOST_CHECK(false); + } + + delete exception_clone; //manual cleanup +} diff --git a/host/tests/gain_group_test.cpp b/host/tests/gain_group_test.cpp new file mode 100644 index 000000000..07eaf146e --- /dev/null +++ b/host/tests/gain_group_test.cpp @@ -0,0 +1,122 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/utils/gain_group.hpp> +#include <boost/bind.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> + +#define rint(x) boost::math::iround(x) + +using namespace uhd; + +/*********************************************************************** + * Define gain element classes with needed functions + **********************************************************************/ +class gain_element1{ +public: + + gain_range_t get_range(void){ + return gain_range_t(0, 90, 1); + } + + double get_value(void){ + return _gain; + } + + void set_value(double gain){ + double step = get_range().step(); + _gain = step*rint(gain/step); + } + +private: + double _gain; +}; + +class gain_element2{ +public: + + gain_range_t get_range(void){ + return gain_range_t(-20, 10, 0.1); + } + + double get_value(void){ + return _gain; + } + + void set_value(double gain){ + double step = get_range().step(); + _gain = step*rint(gain/step); + } + +private: + double _gain; +}; + +//create static instances of gain elements to be shared by the tests +static gain_element1 g1; +static gain_element2 g2; + +static gain_group::sptr get_gain_group(size_t pri1 = 0, size_t pri2 = 0){ + //create instance of gain group + gain_fcns_t gain_fcns; + gain_group::sptr gg(gain_group::make()); + + //load gain group with function sets + gain_fcns.get_range = boost::bind(&gain_element1::get_range, &g1); + gain_fcns.get_value = boost::bind(&gain_element1::get_value, &g1); + gain_fcns.set_value = boost::bind(&gain_element1::set_value, &g1, _1); + gg->register_fcns("g1", gain_fcns, pri1); + + gain_fcns.get_range = boost::bind(&gain_element2::get_range, &g2); + gain_fcns.get_value = boost::bind(&gain_element2::get_value, &g2); + gain_fcns.set_value = boost::bind(&gain_element2::set_value, &g2, _1); + gg->register_fcns("g2", gain_fcns, pri2); + + return gg; +} + +/*********************************************************************** + * Test cases + **********************************************************************/ +static const double tolerance = 0.001; + +BOOST_AUTO_TEST_CASE(test_gain_group_overall){ + gain_group::sptr gg = get_gain_group(); + + //test the overall stuff + gg->set_value(80); + BOOST_CHECK_CLOSE(gg->get_value(), 80.0, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().start(), -20.0, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().stop(), 100.0, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().step(), 0.1, tolerance); +} + +BOOST_AUTO_TEST_CASE(test_gain_group_priority){ + gain_group::sptr gg = get_gain_group(0, 1); + + //test the overall stuff + gg->set_value(80); + BOOST_CHECK_CLOSE(gg->get_value(), 80.0, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().start(), -20.0, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().stop(), 100.0, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().step(), 0.1, tolerance); + + //test the the higher priority gain got filled first (gain 2) + BOOST_CHECK_CLOSE(g2.get_value(), g2.get_range().stop(), tolerance); +} diff --git a/host/tests/module_test.cpp b/host/tests/module_test.cpp new file mode 100644 index 000000000..af2f749a7 --- /dev/null +++ b/host/tests/module_test.cpp @@ -0,0 +1,26 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/static.hpp> +#include <iostream> + +UHD_STATIC_BLOCK(module_test){ + std::cout << "---------------------------------------" << std::endl; + std::cout << "-- Good news, everyone!" << std::endl; + std::cout << "-- The test module has been loaded." << std::endl; + std::cout << "---------------------------------------" << std::endl; +} diff --git a/host/tests/msg_test.cpp b/host/tests/msg_test.cpp new file mode 100644 index 000000000..94b81268c --- /dev/null +++ b/host/tests/msg_test.cpp @@ -0,0 +1,40 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/utils/msg.hpp> +#include <iostream> + +BOOST_AUTO_TEST_CASE(test_messages){ + std::cerr << "---begin print test ---" << std::endl; + UHD_MSG(status) << + "This is a test print for a status message.\n" + "And this is the second line of the test print.\n" + ; + UHD_MSG(warning) << + "This is a test print for a warning message.\n" + "And this is the second line of the test print.\n" + ; + UHD_MSG(error) << + "This is a test print for an error message.\n" + "And this is the second line of the test print.\n" + ; + UHD_HERE(); + const int x = 42; + UHD_VAR(x); + std::cerr << "---end print test ---" << std::endl; +} diff --git a/host/tests/property_test.cpp b/host/tests/property_test.cpp new file mode 100644 index 000000000..04d3a831c --- /dev/null +++ b/host/tests/property_test.cpp @@ -0,0 +1,175 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/property_tree.hpp> +#include <boost/bind.hpp> +#include <exception> +#include <iostream> + +struct coercer_type{ + int doit(int x){ + return x & ~0x3; + } +}; + +struct setter_type{ + void doit(int x){ + _x = x; + } + + int _x; +}; + +struct getter_type{ + int doit(void){ + return _x; + } + + int _x; +}; + +BOOST_AUTO_TEST_CASE(test_prop_simple){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/"); + + BOOST_CHECK(prop.empty()); + prop.set(0); + BOOST_CHECK(not prop.empty()); + + prop.set(42); + BOOST_CHECK_EQUAL(prop.get(), 42); + prop.set(34); + BOOST_CHECK_EQUAL(prop.get(), 34); +} + +BOOST_AUTO_TEST_CASE(test_prop_with_subscriber){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/"); + + setter_type setter; + prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + + prop.set(42); + BOOST_CHECK_EQUAL(prop.get(), 42); + BOOST_CHECK_EQUAL(setter._x, 42); + + prop.set(34); + BOOST_CHECK_EQUAL(prop.get(), 34); + BOOST_CHECK_EQUAL(setter._x, 34); +} + +BOOST_AUTO_TEST_CASE(test_prop_with_publisher){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/"); + + BOOST_CHECK(prop.empty()); + getter_type getter; + prop.publish(boost::bind(&getter_type::doit, &getter)); + BOOST_CHECK(not prop.empty()); + + getter._x = 42; + prop.set(0); //should not change + BOOST_CHECK_EQUAL(prop.get(), 42); + + getter._x = 34; + prop.set(0); //should not change + BOOST_CHECK_EQUAL(prop.get(), 34); +} + +BOOST_AUTO_TEST_CASE(test_prop_with_publisher_and_subscriber){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/"); + + getter_type getter; + prop.publish(boost::bind(&getter_type::doit, &getter)); + + setter_type setter; + prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + + getter._x = 42; + prop.set(0); + BOOST_CHECK_EQUAL(prop.get(), 42); + BOOST_CHECK_EQUAL(setter._x, 0); + + getter._x = 34; + prop.set(1); + BOOST_CHECK_EQUAL(prop.get(), 34); + BOOST_CHECK_EQUAL(setter._x, 1); +} + +BOOST_AUTO_TEST_CASE(test_prop_with_coercion){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/"); + + setter_type setter; + prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + + coercer_type coercer; + prop.coerce(boost::bind(&coercer_type::doit, &coercer, _1)); + + prop.set(42); + BOOST_CHECK_EQUAL(prop.get(), 40); + BOOST_CHECK_EQUAL(setter._x, 40); + + prop.set(34); + BOOST_CHECK_EQUAL(prop.get(), 32); + BOOST_CHECK_EQUAL(setter._x, 32); +} + +BOOST_AUTO_TEST_CASE(test_prop_tree){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + + tree->create<int>("/test/prop0"); + tree->create<int>("/test/prop1"); + + BOOST_CHECK(tree->exists("/test")); + BOOST_CHECK_THROW(tree->access<int>("/test"), std::exception); + BOOST_CHECK(tree->exists("/test/prop0")); + BOOST_CHECK(tree->exists("/test/prop1")); + + tree->access<int>("/test/prop0").set(42); + tree->access<int>("/test/prop1").set(34); + + BOOST_CHECK_EQUAL(tree->access<int>("/test/prop0").get(), 42); + BOOST_CHECK_EQUAL(tree->access<int>("/test/prop1").get(), 34); + + tree->remove("/test/prop0"); + BOOST_CHECK(not tree->exists("/test/prop0")); + BOOST_CHECK(tree->exists("/test/prop1")); + + tree->remove("/test"); + BOOST_CHECK(not tree->exists("/test/prop0")); + BOOST_CHECK(not tree->exists("/test/prop1")); + +} + +BOOST_AUTO_TEST_CASE(test_prop_subtree){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + tree->create<int>("/subdir1/subdir2"); + + uhd::property_tree::sptr subtree1 = tree->subtree("/"); + const std::vector<std::string> tree_dirs1 = tree->list("/"); + const std::vector<std::string> subtree1_dirs = subtree1->list(""); + BOOST_CHECK_EQUAL_COLLECTIONS(tree_dirs1.begin(), tree_dirs1.end(), subtree1_dirs.begin(), subtree1_dirs.end()); + + uhd::property_tree::sptr subtree2 = subtree1->subtree("subdir1"); + const std::vector<std::string> tree_dirs2 = tree->list("/subdir1"); + const std::vector<std::string> subtree2_dirs = subtree2->list(""); + BOOST_CHECK_EQUAL_COLLECTIONS(tree_dirs2.begin(), tree_dirs2.end(), subtree2_dirs.begin(), subtree2_dirs.end()); + +} diff --git a/host/tests/ranges_test.cpp b/host/tests/ranges_test.cpp new file mode 100644 index 000000000..85bb4c3c4 --- /dev/null +++ b/host/tests/ranges_test.cpp @@ -0,0 +1,70 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/types/ranges.hpp> +#include <iostream> + +using namespace uhd; + +static const double tolerance = 0.001; + +BOOST_AUTO_TEST_CASE(test_ranges_bounds){ + meta_range_t mr; + mr.push_back(range_t(-1.0, +1.0, 0.1)); + BOOST_CHECK_CLOSE(mr.start(), -1.0, tolerance); + BOOST_CHECK_CLOSE(mr.stop(), +1.0, tolerance); + BOOST_CHECK_CLOSE(mr.step(), 0.1, tolerance); + + mr.push_back(range_t(40.0, 60.0, 1.0)); + BOOST_CHECK_CLOSE(mr.start(), -1.0, tolerance); + BOOST_CHECK_CLOSE(mr.stop(), 60.0, tolerance); + BOOST_CHECK_CLOSE(mr.step(), 0.1, tolerance); + + BOOST_CHECK_EQUAL(mr.size(), unsigned(2)); + + BOOST_CHECK_CLOSE(mr[0].start(), -1.0, tolerance); + BOOST_CHECK_CLOSE(mr[0].stop(), +1.0, tolerance); + BOOST_CHECK_CLOSE(mr[0].step(), 0.1, tolerance); +} + +BOOST_AUTO_TEST_CASE(test_ranges_clip){ + meta_range_t mr; + mr.push_back(range_t(-1.0, +1.0, 0.1)); + mr.push_back(range_t(40.0, 60.0, 1.0)); + + BOOST_CHECK_CLOSE(mr.clip(-30.0), -1.0, tolerance); + BOOST_CHECK_CLOSE(mr.clip(70.0), 60.0, tolerance); + BOOST_CHECK_CLOSE(mr.clip(20.0), 1.0, tolerance); + BOOST_CHECK_CLOSE(mr.clip(50.0), 50.0, tolerance); + + BOOST_CHECK_CLOSE(mr.clip(50.9, false), 50.9, tolerance); + BOOST_CHECK_CLOSE(mr.clip(50.9, true), 51.0, tolerance); +} + +BOOST_AUTO_TEST_CASE(test_ranges_clip2){ + meta_range_t mr; + mr.push_back(range_t(1.)); + mr.push_back(range_t(2.)); + mr.push_back(range_t(3.)); + + BOOST_CHECK_CLOSE(mr.clip(2., true), 2., tolerance); + BOOST_CHECK_CLOSE(mr.clip(0., true), 1., tolerance); + BOOST_CHECK_CLOSE(mr.clip(1.2, true), 1., tolerance); + BOOST_CHECK_CLOSE(mr.clip(3.1, true), 3., tolerance); + BOOST_CHECK_CLOSE(mr.clip(4., true), 3., tolerance); +} diff --git a/host/tests/sph_recv_test.cpp b/host/tests/sph_recv_test.cpp new file mode 100644 index 000000000..9b45d7016 --- /dev/null +++ b/host/tests/sph_recv_test.cpp @@ -0,0 +1,707 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include "../lib/transport/super_recv_packet_handler.hpp" +#include <boost/shared_array.hpp> +#include <boost/bind.hpp> +#include <complex> +#include <vector> +#include <list> + +#define BOOST_CHECK_TS_CLOSE(a, b) \ + BOOST_CHECK_CLOSE((a).get_real_secs(), (b).get_real_secs(), 0.001) + +/*********************************************************************** + * A dummy overflow handler for testing + **********************************************************************/ +struct overflow_handler_type{ + overflow_handler_type(void){ + num_overflow = 0; + } + void handle(void){ + num_overflow++; + } + size_t num_overflow; +}; + +/*********************************************************************** + * A dummy managed receive buffer for testing + **********************************************************************/ +class dummy_mrb : public uhd::transport::managed_recv_buffer{ +public: + void release(void){ + //NOP + } + + sptr get_new(boost::shared_array<char> mem, size_t len){ + _mem = mem; + _len = len; + return make_managed_buffer(this); + } + +private: + const void *get_buff(void) const{return _mem.get();} + size_t get_size(void) const{return _len;} + + boost::shared_array<char> _mem; + size_t _len; +}; + +/*********************************************************************** + * A dummy transport class to fill with fake data + **********************************************************************/ +class dummy_recv_xport_class{ +public: + dummy_recv_xport_class(const std::string &end){ + _end = end; + } + + void push_back_packet( + uhd::transport::vrt::if_packet_info_t &ifpi, + const boost::uint32_t optional_msg_word = 0 + ){ + const size_t max_pkt_len = (ifpi.num_payload_words32 + uhd::transport::vrt::max_if_hdr_words32 + 1/*tlr*/)*sizeof(boost::uint32_t); + _mems.push_back(boost::shared_array<char>(new char[max_pkt_len])); + if (_end == "big"){ + uhd::transport::vrt::if_hdr_pack_be(reinterpret_cast<boost::uint32_t *>(_mems.back().get()), ifpi); + } + if (_end == "little"){ + uhd::transport::vrt::if_hdr_pack_le(reinterpret_cast<boost::uint32_t *>(_mems.back().get()), ifpi); + } + (reinterpret_cast<boost::uint32_t *>(_mems.back().get()) + ifpi.num_header_words32)[0] = optional_msg_word | uhd::byteswap(optional_msg_word); + _lens.push_back(ifpi.num_packet_words32*sizeof(boost::uint32_t)); + } + + uhd::transport::managed_recv_buffer::sptr get_recv_buff(double){ + if (_mems.empty()) return uhd::transport::managed_recv_buffer::sptr(); //timeout + _mrbs.push_back(dummy_mrb()); + uhd::transport::managed_recv_buffer::sptr mrb = _mrbs.back().get_new(_mems.front(), _lens.front()); + _mems.pop_front(); + _lens.pop_front(); + return mrb; + } + +private: + std::list<boost::shared_array<char> > _mems; + std::list<size_t> _lens; + std::list<dummy_mrb> _mrbs; //list means no-realloc + std::string _end; +}; + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_recv_one_channel_normal){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "sc16_item32_be"; + id.num_inputs = 1; + id.output_format = "fc32"; + id.num_outputs = 1; + + dummy_recv_xport_class dummy_recv_xport("big"); + uhd::transport::vrt::if_packet_info_t ifpi; + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_DATA; + ifpi.num_payload_words32 = 0; + ifpi.packet_count = 0; + ifpi.sob = true; + ifpi.eob = false; + ifpi.has_sid = false; + ifpi.has_cid = false; + ifpi.has_tsi = true; + ifpi.has_tsf = true; + ifpi.tsi = 0; + ifpi.tsf = 0; + ifpi.has_tlr = false; + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + + //generate a bunch of packets + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + ifpi.num_payload_words32 = 10 + i%10; + dummy_recv_xport.push_back_packet(ifpi); + ifpi.packet_count++; + ifpi.tsf += ifpi.num_payload_words32*size_t(TICK_RATE/SAMP_RATE); + } + + //create the super receive packet handler + uhd::transport::sph::recv_packet_handler handler(1); + handler.set_vrt_unpacker(&uhd::transport::vrt::if_hdr_unpack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + handler.set_xport_chan_get_buff(0, boost::bind(&dummy_recv_xport_class::get_recv_buff, &dummy_recv_xport, _1)); + handler.set_converter(id); + + //check the received packets + size_t num_accum_samps = 0; + std::vector<std::complex<float> > buff(20); + uhd::rx_metadata_t metadata; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + size_t num_samps_ret = handler.recv( + &buff.front(), buff.size(), metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_NONE); + BOOST_CHECK(not metadata.more_fragments); + BOOST_CHECK(metadata.has_time_spec); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(num_samps_ret, 10 + i%10); + num_accum_samps += num_samps_ret; + } + + //subsequent receives should be a timeout + for (size_t i = 0; i < 3; i++){ + std::cout << "timeout check " << i << std::endl; + handler.recv( + &buff.front(), buff.size(), metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_TIMEOUT); + } +} + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_recv_one_channel_sequence_error){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "sc16_item32_be"; + id.num_inputs = 1; + id.output_format = "fc32"; + id.num_outputs = 1; + + dummy_recv_xport_class dummy_recv_xport("big"); + uhd::transport::vrt::if_packet_info_t ifpi; + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_DATA; + ifpi.num_payload_words32 = 0; + ifpi.packet_count = 0; + ifpi.sob = true; + ifpi.eob = false; + ifpi.has_sid = false; + ifpi.has_cid = false; + ifpi.has_tsi = true; + ifpi.has_tsf = true; + ifpi.tsi = 0; + ifpi.tsf = 0; + ifpi.has_tlr = false; + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + + //generate a bunch of packets + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + ifpi.num_payload_words32 = 10 + i%10; + if (i != NUM_PKTS_TO_TEST/2){ //simulate a lost packet + dummy_recv_xport.push_back_packet(ifpi); + } + ifpi.packet_count++; + ifpi.tsf += ifpi.num_payload_words32*size_t(TICK_RATE/SAMP_RATE); + } + + //create the super receive packet handler + uhd::transport::sph::recv_packet_handler handler(1); + handler.set_vrt_unpacker(&uhd::transport::vrt::if_hdr_unpack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + handler.set_xport_chan_get_buff(0, boost::bind(&dummy_recv_xport_class::get_recv_buff, &dummy_recv_xport, _1)); + handler.set_converter(id); + + //check the received packets + size_t num_accum_samps = 0; + std::vector<std::complex<float> > buff(20); + uhd::rx_metadata_t metadata; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + size_t num_samps_ret = handler.recv( + &buff.front(), buff.size(), metadata, 1.0, true + ); + if (i == NUM_PKTS_TO_TEST/2){ + //must get the soft overflow here + BOOST_REQUIRE(metadata.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + num_accum_samps += 10 + i%10; + } + else{ + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_NONE); + BOOST_CHECK(not metadata.more_fragments); + BOOST_CHECK(metadata.has_time_spec); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(num_samps_ret, 10 + i%10); + num_accum_samps += num_samps_ret; + } + } + + //subsequent receives should be a timeout + for (size_t i = 0; i < 3; i++){ + std::cout << "timeout check " << i << std::endl; + handler.recv( + &buff.front(), buff.size(), metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_TIMEOUT); + } +} + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_recv_one_channel_inline_message){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "sc16_item32_be"; + id.num_inputs = 1; + id.output_format = "fc32"; + id.num_outputs = 1; + + dummy_recv_xport_class dummy_recv_xport("big"); + uhd::transport::vrt::if_packet_info_t ifpi; + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_DATA; + ifpi.num_payload_words32 = 0; + ifpi.packet_count = 0; + ifpi.sob = true; + ifpi.eob = false; + ifpi.has_sid = false; + ifpi.has_cid = false; + ifpi.has_tsi = true; + ifpi.has_tsf = true; + ifpi.tsi = 0; + ifpi.tsf = 0; + ifpi.has_tlr = false; + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + + //generate a bunch of packets + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_DATA; + ifpi.num_payload_words32 = 10 + i%10; + dummy_recv_xport.push_back_packet(ifpi); + ifpi.packet_count++; + ifpi.tsf += ifpi.num_payload_words32*size_t(TICK_RATE/SAMP_RATE); + + //simulate overflow + if (i == NUM_PKTS_TO_TEST/2){ + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_EXTENSION; + ifpi.num_payload_words32 = 1; + dummy_recv_xport.push_back_packet(ifpi, uhd::rx_metadata_t::ERROR_CODE_OVERFLOW); + } + } + + //create the super receive packet handler + uhd::transport::sph::recv_packet_handler handler(1); + handler.set_vrt_unpacker(&uhd::transport::vrt::if_hdr_unpack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + handler.set_xport_chan_get_buff(0, boost::bind(&dummy_recv_xport_class::get_recv_buff, &dummy_recv_xport, _1)); + handler.set_converter(id); + + //create an overflow handler + overflow_handler_type overflow_handler; + handler.set_overflow_handler(0, boost::bind(&overflow_handler_type::handle, &overflow_handler)); + + //check the received packets + size_t num_accum_samps = 0; + std::vector<std::complex<float> > buff(20); + uhd::rx_metadata_t metadata; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + size_t num_samps_ret = handler.recv( + &buff.front(), buff.size(), metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_NONE); + BOOST_CHECK(not metadata.more_fragments); + BOOST_CHECK(metadata.has_time_spec); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(num_samps_ret, 10 + i%10); + num_accum_samps += num_samps_ret; + if (i == NUM_PKTS_TO_TEST/2){ + handler.recv( + &buff.front(), buff.size(), metadata, 1.0, true + ); + std::cout << "metadata.error_code " << metadata.error_code << std::endl; + BOOST_REQUIRE(metadata.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(overflow_handler.num_overflow, size_t(1)); + } + } + + //subsequent receives should be a timeout + for (size_t i = 0; i < 3; i++){ + std::cout << "timeout check " << i << std::endl; + handler.recv( + &buff.front(), buff.size(), metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_TIMEOUT); + } +} + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_recv_multi_channel_normal){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "sc16_item32_be"; + id.num_inputs = 1; + id.output_format = "fc32"; + id.num_outputs = 1; + + uhd::transport::vrt::if_packet_info_t ifpi; + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_DATA; + ifpi.num_payload_words32 = 0; + ifpi.packet_count = 0; + ifpi.sob = true; + ifpi.eob = false; + ifpi.has_sid = false; + ifpi.has_cid = false; + ifpi.has_tsi = true; + ifpi.has_tsf = true; + ifpi.tsi = 0; + ifpi.tsf = 0; + ifpi.has_tlr = false; + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + static const size_t NUM_SAMPS_PER_BUFF = 20; + static const size_t NCHANNELS = 4; + + std::vector<dummy_recv_xport_class> dummy_recv_xports(NCHANNELS, dummy_recv_xport_class("big")); + + //generate a bunch of packets + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + ifpi.num_payload_words32 = 10 + i%10; + for (size_t ch = 0; ch < NCHANNELS; ch++){ + dummy_recv_xports[ch].push_back_packet(ifpi); + } + ifpi.packet_count++; + ifpi.tsf += ifpi.num_payload_words32*size_t(TICK_RATE/SAMP_RATE); + } + + //create the super receive packet handler + uhd::transport::sph::recv_packet_handler handler(NCHANNELS); + handler.set_vrt_unpacker(&uhd::transport::vrt::if_hdr_unpack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + for (size_t ch = 0; ch < NCHANNELS; ch++){ + handler.set_xport_chan_get_buff(ch, boost::bind(&dummy_recv_xport_class::get_recv_buff, &dummy_recv_xports[ch], _1)); + } + handler.set_converter(id); + + //check the received packets + size_t num_accum_samps = 0; + std::vector<std::complex<float> > mem(NUM_SAMPS_PER_BUFF*NCHANNELS); + std::vector<std::complex<float> *> buffs(NCHANNELS); + for (size_t ch = 0; ch < NCHANNELS; ch++){ + buffs[ch] = &mem[ch*NUM_SAMPS_PER_BUFF]; + } + uhd::rx_metadata_t metadata; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + size_t num_samps_ret = handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_NONE); + BOOST_CHECK(not metadata.more_fragments); + BOOST_CHECK(metadata.has_time_spec); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(num_samps_ret, 10 + i%10); + num_accum_samps += num_samps_ret; + } + + //subsequent receives should be a timeout + for (size_t i = 0; i < 3; i++){ + std::cout << "timeout check " << i << std::endl; + handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_TIMEOUT); + } + +} + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_recv_multi_channel_sequence_error){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "sc16_item32_be"; + id.num_inputs = 1; + id.output_format = "fc32"; + id.num_outputs = 1; + + uhd::transport::vrt::if_packet_info_t ifpi; + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_DATA; + ifpi.num_payload_words32 = 0; + ifpi.packet_count = 0; + ifpi.sob = true; + ifpi.eob = false; + ifpi.has_sid = false; + ifpi.has_cid = false; + ifpi.has_tsi = true; + ifpi.has_tsf = true; + ifpi.tsi = 0; + ifpi.tsf = 0; + ifpi.has_tlr = false; + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + static const size_t NUM_SAMPS_PER_BUFF = 20; + static const size_t NCHANNELS = 4; + + std::vector<dummy_recv_xport_class> dummy_recv_xports(NCHANNELS, dummy_recv_xport_class("big")); + + //generate a bunch of packets + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + ifpi.num_payload_words32 = 10 + i%10; + for (size_t ch = 0; ch < NCHANNELS; ch++){ + if (i == NUM_PKTS_TO_TEST/2 and ch == 2){ + continue; //simulates a lost packet + } + dummy_recv_xports[ch].push_back_packet(ifpi); + } + ifpi.packet_count++; + ifpi.tsf += ifpi.num_payload_words32*size_t(TICK_RATE/SAMP_RATE); + } + + //create the super receive packet handler + uhd::transport::sph::recv_packet_handler handler(NCHANNELS); + handler.set_vrt_unpacker(&uhd::transport::vrt::if_hdr_unpack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + for (size_t ch = 0; ch < NCHANNELS; ch++){ + handler.set_xport_chan_get_buff(ch, boost::bind(&dummy_recv_xport_class::get_recv_buff, &dummy_recv_xports[ch], _1)); + } + handler.set_converter(id); + + //check the received packets + size_t num_accum_samps = 0; + std::vector<std::complex<float> > mem(NUM_SAMPS_PER_BUFF*NCHANNELS); + std::vector<std::complex<float> *> buffs(NCHANNELS); + for (size_t ch = 0; ch < NCHANNELS; ch++){ + buffs[ch] = &mem[ch*NUM_SAMPS_PER_BUFF]; + } + uhd::rx_metadata_t metadata; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + size_t num_samps_ret = handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + if (i == NUM_PKTS_TO_TEST/2){ + //must get the soft overflow here + BOOST_REQUIRE(metadata.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + num_accum_samps += 10 + i%10; + } + else{ + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_NONE); + BOOST_CHECK(not metadata.more_fragments); + BOOST_CHECK(metadata.has_time_spec); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(num_samps_ret, 10 + i%10); + num_accum_samps += num_samps_ret; + } + } + + //subsequent receives should be a timeout + for (size_t i = 0; i < 3; i++){ + std::cout << "timeout check " << i << std::endl; + handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_TIMEOUT); + } +} + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_recv_multi_channel_time_error){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "sc16_item32_be"; + id.num_inputs = 1; + id.output_format = "fc32"; + id.num_outputs = 1; + + uhd::transport::vrt::if_packet_info_t ifpi; + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_DATA; + ifpi.num_payload_words32 = 0; + ifpi.packet_count = 0; + ifpi.sob = true; + ifpi.eob = false; + ifpi.has_sid = false; + ifpi.has_cid = false; + ifpi.has_tsi = true; + ifpi.has_tsf = true; + ifpi.tsi = 0; + ifpi.tsf = 0; + ifpi.has_tlr = false; + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + static const size_t NUM_SAMPS_PER_BUFF = 20; + static const size_t NCHANNELS = 4; + + std::vector<dummy_recv_xport_class> dummy_recv_xports(NCHANNELS, dummy_recv_xport_class("big")); + + //generate a bunch of packets + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + ifpi.num_payload_words32 = 10 + i%10; + for (size_t ch = 0; ch < NCHANNELS; ch++){ + dummy_recv_xports[ch].push_back_packet(ifpi); + } + ifpi.packet_count++; + ifpi.tsf += ifpi.num_payload_words32*size_t(TICK_RATE/SAMP_RATE); + if (i == NUM_PKTS_TO_TEST/2){ + ifpi.tsf = 0; //simulate the user changing the time + } + } + + //create the super receive packet handler + uhd::transport::sph::recv_packet_handler handler(NCHANNELS); + handler.set_vrt_unpacker(&uhd::transport::vrt::if_hdr_unpack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + for (size_t ch = 0; ch < NCHANNELS; ch++){ + handler.set_xport_chan_get_buff(ch, boost::bind(&dummy_recv_xport_class::get_recv_buff, &dummy_recv_xports[ch], _1)); + } + handler.set_converter(id); + + //check the received packets + size_t num_accum_samps = 0; + std::vector<std::complex<float> > mem(NUM_SAMPS_PER_BUFF*NCHANNELS); + std::vector<std::complex<float> *> buffs(NCHANNELS); + for (size_t ch = 0; ch < NCHANNELS; ch++){ + buffs[ch] = &mem[ch*NUM_SAMPS_PER_BUFF]; + } + uhd::rx_metadata_t metadata; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + size_t num_samps_ret = handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_NONE); + BOOST_CHECK(not metadata.more_fragments); + BOOST_CHECK(metadata.has_time_spec); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(num_samps_ret, 10 + i%10); + num_accum_samps += num_samps_ret; + if (i == NUM_PKTS_TO_TEST/2){ + num_accum_samps = 0; //simulate the user changing the time + } + } + + //subsequent receives should be a timeout + for (size_t i = 0; i < 3; i++){ + std::cout << "timeout check " << i << std::endl; + handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_TIMEOUT); + } +} + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_recv_multi_channel_fragment){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "sc16_item32_be"; + id.num_inputs = 1; + id.output_format = "fc32"; + id.num_outputs = 1; + + uhd::transport::vrt::if_packet_info_t ifpi; + ifpi.packet_type = uhd::transport::vrt::if_packet_info_t::PACKET_TYPE_DATA; + ifpi.num_payload_words32 = 0; + ifpi.packet_count = 0; + ifpi.sob = true; + ifpi.eob = false; + ifpi.has_sid = false; + ifpi.has_cid = false; + ifpi.has_tsi = true; + ifpi.has_tsf = true; + ifpi.tsi = 0; + ifpi.tsf = 0; + ifpi.has_tlr = false; + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + static const size_t NUM_SAMPS_PER_BUFF = 10; + static const size_t NCHANNELS = 4; + + std::vector<dummy_recv_xport_class> dummy_recv_xports(NCHANNELS, dummy_recv_xport_class("big")); + + //generate a bunch of packets + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + ifpi.num_payload_words32 = 10 + i%10; + for (size_t ch = 0; ch < NCHANNELS; ch++){ + dummy_recv_xports[ch].push_back_packet(ifpi); + } + ifpi.packet_count++; + ifpi.tsf += ifpi.num_payload_words32*size_t(TICK_RATE/SAMP_RATE); + } + + //create the super receive packet handler + uhd::transport::sph::recv_packet_handler handler(NCHANNELS); + handler.set_vrt_unpacker(&uhd::transport::vrt::if_hdr_unpack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + for (size_t ch = 0; ch < NCHANNELS; ch++){ + handler.set_xport_chan_get_buff(ch, boost::bind(&dummy_recv_xport_class::get_recv_buff, &dummy_recv_xports[ch], _1)); + } + handler.set_converter(id); + + //check the received packets + size_t num_accum_samps = 0; + std::vector<std::complex<float> > mem(NUM_SAMPS_PER_BUFF*NCHANNELS); + std::vector<std::complex<float> *> buffs(NCHANNELS); + for (size_t ch = 0; ch < NCHANNELS; ch++){ + buffs[ch] = &mem[ch*NUM_SAMPS_PER_BUFF]; + } + uhd::rx_metadata_t metadata; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + size_t num_samps_ret = handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_NONE); + BOOST_CHECK(metadata.has_time_spec); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(num_samps_ret, 10); + num_accum_samps += num_samps_ret; + + if (not metadata.more_fragments) continue; + + num_samps_ret = handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_NONE); + BOOST_CHECK(not metadata.more_fragments); + BOOST_CHECK_EQUAL(metadata.fragment_offset, 10); + BOOST_CHECK(metadata.has_time_spec); + BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); + BOOST_CHECK_EQUAL(num_samps_ret, i%10); + num_accum_samps += num_samps_ret; + } + + //subsequent receives should be a timeout + for (size_t i = 0; i < 3; i++){ + std::cout << "timeout check " << i << std::endl; + handler.recv( + buffs, NUM_SAMPS_PER_BUFF, metadata, 1.0, true + ); + BOOST_CHECK_EQUAL(metadata.error_code, uhd::rx_metadata_t::ERROR_CODE_TIMEOUT); + } + +} diff --git a/host/tests/sph_send_test.cpp b/host/tests/sph_send_test.cpp new file mode 100644 index 000000000..c31399d12 --- /dev/null +++ b/host/tests/sph_send_test.cpp @@ -0,0 +1,198 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include "../lib/transport/super_send_packet_handler.hpp" +#include <boost/shared_array.hpp> +#include <boost/bind.hpp> +#include <complex> +#include <vector> +#include <list> + +#define BOOST_CHECK_TS_CLOSE(a, b) \ + BOOST_CHECK_CLOSE((a).get_real_secs(), (b).get_real_secs(), 0.001) + +/*********************************************************************** + * A dummy managed send buffer for testing + **********************************************************************/ +class dummy_msb : public uhd::transport::managed_send_buffer{ +public: + void commit(size_t len){ + if (len == 0) return; + *_len = len; + } + + sptr get_new(boost::shared_array<char> mem, size_t *len){ + _mem = mem; + _len = len; + return make_managed_buffer(this); + } + +private: + void *get_buff(void) const{return _mem.get();} + size_t get_size(void) const{return *_len;} + + boost::shared_array<char> _mem; + size_t *_len; +}; + +/*********************************************************************** + * A dummy transport class to fill with fake data + **********************************************************************/ +class dummy_send_xport_class{ +public: + dummy_send_xport_class(const std::string &end){ + _end = end; + } + + void pop_front_packet( + uhd::transport::vrt::if_packet_info_t &ifpi + ){ + ifpi.num_packet_words32 = _lens.front()/sizeof(boost::uint32_t); + if (_end == "big"){ + uhd::transport::vrt::if_hdr_unpack_be(reinterpret_cast<boost::uint32_t *>(_mems.front().get()), ifpi); + } + if (_end == "little"){ + uhd::transport::vrt::if_hdr_unpack_le(reinterpret_cast<boost::uint32_t *>(_mems.front().get()), ifpi); + } + _mems.pop_front(); + _lens.pop_front(); + } + + uhd::transport::managed_send_buffer::sptr get_send_buff(double){ + _msbs.push_back(dummy_msb()); + _mems.push_back(boost::shared_array<char>(new char[1000])); + _lens.push_back(1000); + uhd::transport::managed_send_buffer::sptr mrb = _msbs.back().get_new(_mems.back(), &_lens.back()); + return mrb; + } + +private: + std::list<boost::shared_array<char> > _mems; + std::list<size_t> _lens; + std::list<dummy_msb> _msbs; //list means no-realloc + std::string _end; +}; + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_send_one_channel_one_packet_mode){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.output_format = "sc16_item32_be"; + id.num_outputs = 1; + + dummy_send_xport_class dummy_send_xport("big"); + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + + //create the super send packet handler + uhd::transport::sph::send_packet_handler handler(1); + handler.set_vrt_packer(&uhd::transport::vrt::if_hdr_pack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + handler.set_xport_chan_get_buff(0, boost::bind(&dummy_send_xport_class::get_send_buff, &dummy_send_xport, _1)); + handler.set_converter(id); + handler.set_max_samples_per_packet(20); + + //allocate metadata and buffer + std::vector<std::complex<float> > buff(20); + uhd::tx_metadata_t metadata; + metadata.has_time_spec = true; + metadata.time_spec = uhd::time_spec_t(0.0); + + //generate the test data + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + metadata.start_of_burst = (i == 0); + metadata.end_of_burst = (i == NUM_PKTS_TO_TEST-1); + const size_t num_sent = handler.send( + &buff.front(), 10 + i%10, metadata, 1.0 + ); + BOOST_CHECK_EQUAL(num_sent, 10 + i%10); + metadata.time_spec += uhd::time_spec_t(0, num_sent, SAMP_RATE); + } + + //check the sent packets + size_t num_accum_samps = 0; + uhd::transport::vrt::if_packet_info_t ifpi; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + dummy_send_xport.pop_front_packet(ifpi); + BOOST_CHECK_EQUAL(ifpi.num_payload_words32, 10+i%10); + BOOST_CHECK(ifpi.has_tsf); + BOOST_CHECK_EQUAL(ifpi.tsf, num_accum_samps*TICK_RATE/SAMP_RATE); + BOOST_CHECK_EQUAL(ifpi.sob, i == 0); + BOOST_CHECK_EQUAL(ifpi.eob, i == NUM_PKTS_TO_TEST-1); + num_accum_samps += ifpi.num_payload_words32; + } +} + +//////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE(test_sph_send_one_channel_full_buffer_mode){ +//////////////////////////////////////////////////////////////////////// + uhd::convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.output_format = "sc16_item32_be"; + id.num_outputs = 1; + + dummy_send_xport_class dummy_send_xport("big"); + + static const double TICK_RATE = 100e6; + static const double SAMP_RATE = 10e6; + static const size_t NUM_PKTS_TO_TEST = 30; + + //create the super send packet handler + uhd::transport::sph::send_packet_handler handler(1); + handler.set_vrt_packer(&uhd::transport::vrt::if_hdr_pack_be); + handler.set_tick_rate(TICK_RATE); + handler.set_samp_rate(SAMP_RATE); + handler.set_xport_chan_get_buff(0, boost::bind(&dummy_send_xport_class::get_send_buff, &dummy_send_xport, _1)); + handler.set_converter(id); + handler.set_max_samples_per_packet(20); + + //allocate metadata and buffer + std::vector<std::complex<float> > buff(20*NUM_PKTS_TO_TEST); + uhd::tx_metadata_t metadata; + metadata.start_of_burst = true; + metadata.end_of_burst = true; + metadata.has_time_spec = true; + metadata.time_spec = uhd::time_spec_t(0.0); + + //generate the test data + const size_t num_sent = handler.send( + &buff.front(), buff.size(), metadata, 1.0 + ); + BOOST_CHECK_EQUAL(num_sent, buff.size()); + + //check the sent packets + size_t num_accum_samps = 0; + uhd::transport::vrt::if_packet_info_t ifpi; + for (size_t i = 0; i < NUM_PKTS_TO_TEST; i++){ + std::cout << "data check " << i << std::endl; + dummy_send_xport.pop_front_packet(ifpi); + BOOST_CHECK_EQUAL(ifpi.num_payload_words32, 20); + BOOST_CHECK(ifpi.has_tsf); + BOOST_CHECK_EQUAL(ifpi.tsf, num_accum_samps*TICK_RATE/SAMP_RATE); + BOOST_CHECK_EQUAL(ifpi.sob, i == 0); + BOOST_CHECK_EQUAL(ifpi.eob, i == NUM_PKTS_TO_TEST-1); + num_accum_samps += ifpi.num_payload_words32; + } +} diff --git a/host/tests/subdev_spec_test.cpp b/host/tests/subdev_spec_test.cpp new file mode 100644 index 000000000..aa0b9a119 --- /dev/null +++ b/host/tests/subdev_spec_test.cpp @@ -0,0 +1,45 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <boost/foreach.hpp> +#include <iostream> + +BOOST_AUTO_TEST_CASE(test_subdevice_spec){ + std::cout << "Testing subdevice specification..." << std::endl; + + //load the subdev spec with something + uhd::usrp::subdev_spec_t sd_spec; + sd_spec.push_back(uhd::usrp::subdev_spec_pair_t("A", "AB")); + sd_spec.push_back(uhd::usrp::subdev_spec_pair_t("B", "AB")); + + //convert to and from args string + std::cout << "Pretty Print: " << std::endl << sd_spec.to_pp_string(); + std::string markup_str = sd_spec.to_string(); + std::cout << "Markup String: " << markup_str << std::endl; + uhd::usrp::subdev_spec_t new_sd_spec(markup_str); + + //they should be the same size + BOOST_REQUIRE_EQUAL(sd_spec.size(), new_sd_spec.size()); + + //the contents should match + for (size_t i = 0; i < sd_spec.size(); i++){ + BOOST_CHECK_EQUAL(sd_spec.at(i).db_name, new_sd_spec.at(i).db_name); + BOOST_CHECK_EQUAL(sd_spec.at(i).sd_name, new_sd_spec.at(i).sd_name); + } +} diff --git a/host/tests/time_spec_test.cpp b/host/tests/time_spec_test.cpp new file mode 100644 index 000000000..102b7cda3 --- /dev/null +++ b/host/tests/time_spec_test.cpp @@ -0,0 +1,99 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/types/time_spec.hpp> +#include <boost/foreach.hpp> +#include <boost/thread.hpp> //sleep +#include <iostream> + +BOOST_AUTO_TEST_CASE(test_time_spec_compare){ + std::cout << "Testing time specification compare..." << std::endl; + + BOOST_CHECK(uhd::time_spec_t(2.0) == uhd::time_spec_t(2.0)); + BOOST_CHECK(uhd::time_spec_t(2.0) > uhd::time_spec_t(1.0)); + BOOST_CHECK(uhd::time_spec_t(1.0) < uhd::time_spec_t(2.0)); + + BOOST_CHECK(uhd::time_spec_t(1.1) == uhd::time_spec_t(1.1)); + BOOST_CHECK(uhd::time_spec_t(1.1) > uhd::time_spec_t(1.0)); + BOOST_CHECK(uhd::time_spec_t(1.0) < uhd::time_spec_t(1.1)); + + BOOST_CHECK(uhd::time_spec_t(0.1) == uhd::time_spec_t(0.1)); + BOOST_CHECK(uhd::time_spec_t(0.2) > uhd::time_spec_t(0.1)); + BOOST_CHECK(uhd::time_spec_t(0.1) < uhd::time_spec_t(0.2)); +} + +#define CHECK_TS_EQUAL(lhs, rhs) \ + BOOST_CHECK_CLOSE((lhs).get_real_secs(), (rhs).get_real_secs(), 0.001) + +BOOST_AUTO_TEST_CASE(test_time_spec_arithmetic){ + std::cout << "Testing time specification arithmetic..." << std::endl; + + CHECK_TS_EQUAL(uhd::time_spec_t(2.3) + uhd::time_spec_t(1.0), uhd::time_spec_t(3.3)); + CHECK_TS_EQUAL(uhd::time_spec_t(2.3) - uhd::time_spec_t(1.0), uhd::time_spec_t(1.3)); + CHECK_TS_EQUAL(uhd::time_spec_t(1.0) + uhd::time_spec_t(2.3), uhd::time_spec_t(3.3)); + CHECK_TS_EQUAL(uhd::time_spec_t(1.0) - uhd::time_spec_t(2.3), uhd::time_spec_t(-1.3)); +} + +BOOST_AUTO_TEST_CASE(test_time_spec_parts){ + std::cout << "Testing time specification parts..." << std::endl; + + BOOST_CHECK_EQUAL(uhd::time_spec_t(1.1).get_full_secs(), 1); + BOOST_CHECK_CLOSE(uhd::time_spec_t(1.1).get_frac_secs(), 0.1, 0.001); + BOOST_CHECK_EQUAL(uhd::time_spec_t(1.1).to_ticks(100), 110); + + BOOST_CHECK_EQUAL(uhd::time_spec_t(-1.1).get_full_secs(), -2); + BOOST_CHECK_CLOSE(uhd::time_spec_t(-1.1).get_frac_secs(), 0.9, 0.001); + BOOST_CHECK_EQUAL(uhd::time_spec_t(-1.1).to_ticks(100), -110); +} + +BOOST_AUTO_TEST_CASE(test_time_spec_get_system_time){ + std::cout << "Testing time specification get system time..." << std::endl; + + //Not really checking for high resolution timing here, + //just need to check that system time is minimally working. + + uhd::time_spec_t start = uhd::time_spec_t::get_system_time(); + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + uhd::time_spec_t stop = uhd::time_spec_t::get_system_time(); + + uhd::time_spec_t diff = stop - start; + std::cout << "start: " << start.get_real_secs() << std::endl; + std::cout << "stop: " << stop.get_real_secs() << std::endl; + std::cout << "diff: " << diff.get_real_secs() << std::endl; + BOOST_CHECK(diff.get_real_secs() > 0); //assert positive + BOOST_CHECK(diff.get_real_secs() < 1.0); //assert under 1s +} + +BOOST_AUTO_TEST_CASE(test_time_spec_neg_values){ + uhd::time_spec_t ts1(0.3); + uhd::time_spec_t ts2(1, -0.9); + std::cout << "ts1 " << ts1.get_real_secs() << std::endl; + std::cout << "ts2 " << ts2.get_real_secs() << std::endl; + BOOST_CHECK(ts1 > ts2); + + uhd::time_spec_t tsa(430.001083); + uhd::time_spec_t tsb(429.999818); + uhd::time_spec_t tsc(0.3); + uhd::time_spec_t tsd = tsa - tsb; + std::cout << "tsa " << tsa.get_real_secs() << std::endl; + std::cout << "tsb " << tsb.get_real_secs() << std::endl; + std::cout << "tsc " << tsc.get_real_secs() << std::endl; + std::cout << "tsd " << tsd.get_real_secs() << std::endl; + BOOST_CHECK(tsa > tsb); + BOOST_CHECK(tsc > tsd); +} diff --git a/host/tests/vrt_test.cpp b/host/tests/vrt_test.cpp new file mode 100644 index 000000000..066f1493b --- /dev/null +++ b/host/tests/vrt_test.cpp @@ -0,0 +1,141 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <cstdlib> + +using namespace uhd::transport; + +static void pack_and_unpack( + vrt::if_packet_info_t &if_packet_info_in +){ + boost::uint32_t header_buff[vrt::max_if_hdr_words32]; + + //pack metadata into a vrt header + vrt::if_hdr_pack_be( + header_buff, if_packet_info_in + ); + + vrt::if_packet_info_t if_packet_info_out; + if_packet_info_out.num_packet_words32 = if_packet_info_in.num_packet_words32; + + //unpack the vrt header back into metadata + vrt::if_hdr_unpack_be( + header_buff, if_packet_info_out + ); + + //check the the unpacked metadata is the same + BOOST_CHECK_EQUAL(if_packet_info_in.packet_count, if_packet_info_out.packet_count); + BOOST_CHECK_EQUAL(if_packet_info_in.num_header_words32, if_packet_info_out.num_header_words32); + BOOST_CHECK_EQUAL(if_packet_info_in.num_payload_words32, if_packet_info_out.num_payload_words32); + BOOST_CHECK_EQUAL(if_packet_info_in.has_sid, if_packet_info_out.has_sid); + if (if_packet_info_in.has_sid and if_packet_info_out.has_sid){ + BOOST_CHECK_EQUAL(if_packet_info_in.sid, if_packet_info_out.sid); + } + BOOST_CHECK_EQUAL(if_packet_info_in.has_cid, if_packet_info_out.has_cid); + if (if_packet_info_in.has_cid and if_packet_info_out.has_cid){ + BOOST_CHECK_EQUAL(if_packet_info_in.cid, if_packet_info_out.cid); + } + BOOST_CHECK_EQUAL(if_packet_info_in.has_tsi, if_packet_info_out.has_tsi); + if (if_packet_info_in.has_tsi and if_packet_info_out.has_tsi){ + BOOST_CHECK_EQUAL(if_packet_info_in.tsi, if_packet_info_out.tsi); + } + BOOST_CHECK_EQUAL(if_packet_info_in.has_tsf, if_packet_info_out.has_tsf); + if (if_packet_info_in.has_tsf and if_packet_info_out.has_tsf){ + BOOST_CHECK_EQUAL(if_packet_info_in.tsf, if_packet_info_out.tsf); + } + BOOST_CHECK_EQUAL(if_packet_info_in.has_tlr, if_packet_info_out.has_tlr); + if (if_packet_info_in.has_tlr and if_packet_info_out.has_tlr){ + BOOST_CHECK_EQUAL(if_packet_info_in.tlr, if_packet_info_out.tlr); + } +} + +/*********************************************************************** + * Loopback test the vrt packer/unpacker with various packet info combos + * The trailer is not tested as it is not convenient to do so. + **********************************************************************/ + +BOOST_AUTO_TEST_CASE(test_with_none){ + vrt::if_packet_info_t if_packet_info; + if_packet_info.packet_count = 0; + if_packet_info.has_sid = false; + if_packet_info.has_cid = false; + if_packet_info.has_tsi = false; + if_packet_info.has_tsf = false; + if_packet_info.has_tlr = false; + if_packet_info.num_payload_words32 = 0; + pack_and_unpack(if_packet_info); +} + +BOOST_AUTO_TEST_CASE(test_with_sid){ + vrt::if_packet_info_t if_packet_info; + if_packet_info.packet_count = 1; + if_packet_info.has_sid = true; + if_packet_info.has_cid = false; + if_packet_info.has_tsi = false; + if_packet_info.has_tsf = false; + if_packet_info.has_tlr = false; + if_packet_info.sid = std::rand(); + if_packet_info.num_payload_words32 = 1111; + pack_and_unpack(if_packet_info); +} + +static const bool cid_enb = false; + +BOOST_AUTO_TEST_CASE(test_with_cid){ + vrt::if_packet_info_t if_packet_info; + if_packet_info.packet_count = 2; + if_packet_info.has_sid = false; + if_packet_info.has_cid = cid_enb; + if_packet_info.has_tsi = false; + if_packet_info.has_tsf = false; + if_packet_info.has_tlr = false; + if_packet_info.cid = std::rand(); + if_packet_info.num_payload_words32 = 2222; + pack_and_unpack(if_packet_info); +} + +BOOST_AUTO_TEST_CASE(test_with_time){ + vrt::if_packet_info_t if_packet_info; + if_packet_info.packet_count = 3; + if_packet_info.has_sid = false; + if_packet_info.has_cid = false; + if_packet_info.has_tsi = true; + if_packet_info.has_tsf = true; + if_packet_info.has_tlr = false; + if_packet_info.tsi = std::rand(); + if_packet_info.tsf = std::rand(); + if_packet_info.num_payload_words32 = 33333; + pack_and_unpack(if_packet_info); +} + +BOOST_AUTO_TEST_CASE(test_with_all){ + vrt::if_packet_info_t if_packet_info; + if_packet_info.packet_count = 4; + if_packet_info.has_sid = true; + if_packet_info.has_cid = cid_enb; + if_packet_info.has_tsi = true; + if_packet_info.has_tsf = true; + if_packet_info.has_tlr = false; + if_packet_info.sid = std::rand(); + if_packet_info.cid = std::rand(); + if_packet_info.tsi = std::rand(); + if_packet_info.tsf = std::rand(); + if_packet_info.num_payload_words32 = 44444; + pack_and_unpack(if_packet_info); +} diff --git a/host/uhd.pc.in b/host/uhd.pc.in new file mode 100644 index 000000000..b27edfd07 --- /dev/null +++ b/host/uhd.pc.in @@ -0,0 +1,15 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/@LIBRARY_DIR@ +includedir=${prefix}/@INCLUDE_DIR@ + +Name: @CPACK_PACKAGE_NAME@ +Description: @CPACK_PACKAGE_DESCRIPTION_SUMMARY@ +URL: http://code.ettus.com/redmine/ettus/projects/uhd/wiki +Version: @CPACK_PACKAGE_VERSION@ +Requires: +Requires.private: @UHD_PC_REQUIRES@ +Conflicts: +Cflags: -I${includedir} @UHD_PC_CFLAGS@ +Libs: -L${libdir} -luhd +Libs.private: @UHD_PC_LIBS@ diff --git a/host/usrp_e_utils/CMakeLists.txt b/host/usrp_e_utils/CMakeLists.txt new file mode 100644 index 000000000..0d6763174 --- /dev/null +++ b/host/usrp_e_utils/CMakeLists.txt @@ -0,0 +1,42 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# USRP embedded utilities that get installed into the share path +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP-E Utils" ENABLE_USRP_E_UTILS OFF "LINUX" OFF) + +IF(ENABLE_USRP_E_UTILS) + ENABLE_LANGUAGE(C) + INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/usrp/e100) + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/usrp/e100/include) + + SET(usrp_e_utils_sources + usrp-e-loopback.cpp + usrp-e-wb-test.cpp + ) + + #for each source: build an executable and install + FOREACH(util_source ${usrp_e_utils_sources}) + GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) + ADD_EXECUTABLE(${util_name} ${util_source}) + TARGET_LINK_LIBRARIES(${util_name} ${Boost_LIBRARIES}) + INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/usrp_e_utils) + ENDFOREACH(util_source) + +ENDIF(ENABLE_USRP_E_UTILS) diff --git a/host/usrp_e_utils/common.hpp b/host/usrp_e_utils/common.hpp new file mode 100644 index 000000000..989aecd06 --- /dev/null +++ b/host/usrp_e_utils/common.hpp @@ -0,0 +1,64 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <sys/ioctl.h> //ioctl +#include <fcntl.h> //open, close + +#include <linux/usrp_e.h> +#include "e100_regs.hpp" + +static int fp; + +static inline int peek16(int reg){ + int ret; + struct usrp_e_ctl16 d; + + d.offset = reg; + d.count = 1; + ret = ioctl(fp, USRP_E_READ_CTL16, &d); + return d.buf[0]; +} + +static inline void poke16(int reg, int val){ + int ret; + struct usrp_e_ctl16 d; + + d.offset = reg; + d.count = 1; + d.buf[0] = val; + ret = ioctl(fp, USRP_E_WRITE_CTL16, &d); +} + +static inline int peek32(int reg){ + int ret; + struct usrp_e_ctl32 d; + + d.offset = reg; + d.count = 1; + ret = ioctl(fp, USRP_E_READ_CTL32, &d); + return d.buf[0]; +} + +static inline void poke32(int reg, int val){ + int ret; + struct usrp_e_ctl32 d; + + d.offset = reg; + d.count = 1; + d.buf[0] = val; + ret = ioctl(fp, USRP_E_WRITE_CTL32, &d); +} diff --git a/host/usrp_e_utils/usrp-e-loopback.cpp b/host/usrp_e_utils/usrp-e-loopback.cpp new file mode 100644 index 000000000..d4220adc3 --- /dev/null +++ b/host/usrp_e_utils/usrp-e-loopback.cpp @@ -0,0 +1,298 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "common.hpp" +#include <cstdlib> +#include <cstdio> +#include <cstring> +#include <ctime> +#include <iostream> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/cstdint.hpp> +#include <sys/mman.h> //mmap +#include <unistd.h> //getpagesize +#include <poll.h> //poll + +static const size_t bytes_per_frame = 2048; + +static const int poll_timeout_ms = 100; + +struct loopback_pkt_hdr_type{ + boost::uint32_t words32; + boost::uint32_t checksum; + boost::uint64_t seq_num; +}; + +struct loopback_pkt_type{ + loopback_pkt_hdr_type hdr; + boost::uint32_t data[(bytes_per_frame-sizeof(loopback_pkt_hdr_type))/sizeof(boost::uint32_t)]; +}; + +static ring_buffer_info (*recv_info)[]; +static ring_buffer_info (*send_info)[]; +static loopback_pkt_type (*recv_buff)[]; +static loopback_pkt_type (*send_buff)[]; + +static struct usrp_e_ring_buffer_size_t rb_size; + +static bool running = true; + +static boost::uint64_t seq_errors = 0; +static boost::uint64_t checksum_errors = 0; +static boost::uint64_t sent_words32 = 0; +static boost::uint64_t recvd_words32 = 0; + +static inline void print_pkt(const loopback_pkt_type &pkt){ + std::cout << std::endl; + std::cout << "pkt.hdr.words32 " << pkt.hdr.words32 << std::endl; + std::cout << "pkt.hdr.checksum " << pkt.hdr.checksum << std::endl; + std::cout << "pkt.hdr.seq_num " << pkt.hdr.seq_num << std::endl; +} + +boost::uint32_t my_checksum(void *buff, size_t size32){ + boost::uint32_t x = 0; + for (size_t i = 0; i < size32; i++){ + x += reinterpret_cast<boost::uint32_t *>(buff)[i]; + x ^= reinterpret_cast<boost::uint32_t *>(buff)[i]; + } + return x; +} + +/*********************************************************************** + * Read thread - recv frames and verify checksum + **********************************************************************/ +static void read_thread(void){ + std::cout << "start read thread... " << std::endl; + + boost::uint64_t seq_num = 0; + size_t index = 0; + + while (running){ + + loopback_pkt_type &pkt = (*recv_buff)[index]; + ring_buffer_info &info = (*recv_info)[index]; + + //wait for frame available + if (not (info.flags & RB_USER)){ + pollfd pfd; + pfd.fd = fp; + pfd.events = POLLIN; + if (poll(&pfd, 1, poll_timeout_ms) <= 0){ + std::cout << "Read poll timeout, exiting read thread!" << std::endl; + running = false; + return; + } + } + info.flags = RB_USER_PROCESS; + + //print_pkt(pkt); + + //handle checksum + const boost::uint32_t expected_checksum = pkt.hdr.checksum; + pkt.hdr.checksum = 0; //set to zero for calculation + const boost::uint32_t actual_checksum = my_checksum(&pkt, pkt.hdr.words32); + if (expected_checksum != actual_checksum){ + checksum_errors++; + std::cerr << "C"; + } + else{ + recvd_words32 += pkt.hdr.words32; + } + + //handle sequence + if (seq_num != pkt.hdr.seq_num){ + seq_errors++; + std::cerr << "S"; + } + seq_num = pkt.hdr.seq_num+1; + + //release the packet + info.flags = RB_KERNEL; + + //increment index and wrap around to zero + index++; + if (index == size_t(rb_size.num_rx_frames)) index = 0; + } + +} + +/*********************************************************************** + * Write thread - send frames and calculate checksum + **********************************************************************/ +static void write_thread(const size_t num_words32){ + std::cout << "start write thread... " << std::endl; + + srandom(std::time(NULL)); + + boost::uint64_t seq_num = 0; + size_t index = 0; + + //write into tmp and memcopy into pkt to avoid cache issues + loopback_pkt_type pkt_tmp; + + while (running){ + + ring_buffer_info &info = (*send_info)[index]; + + //wait for frame available + if (not (info.flags & RB_KERNEL)){ + pollfd pfd; + pfd.fd = fp; + pfd.events = POLLOUT; + if (poll(&pfd, 1, poll_timeout_ms) <= 0){ + std::cout << "Write poll timeout, exiting write thread!" << std::endl; + running = false; + return; + } + } + + //fill packet header and body + const boost::uint32_t seed = random(); + pkt_tmp.hdr.words32 = sizeof(pkt_tmp.hdr)/sizeof(boost::uint32_t) + num_words32; + pkt_tmp.hdr.checksum = 0; //set to zero for checksum() + pkt_tmp.hdr.seq_num = seq_num++; + for (size_t i = 0; i < num_words32; i++) pkt_tmp.data[i] = seed + i; + pkt_tmp.hdr.checksum = my_checksum(&pkt_tmp, pkt_tmp.hdr.words32); + sent_words32 += pkt_tmp.hdr.words32; + + loopback_pkt_type &pkt = (*send_buff)[index]; + std::memcpy(&pkt, &pkt_tmp, pkt_tmp.hdr.words32*sizeof(boost::uint32_t)); + + //print_pkt(pkt); + + //commit the packet + info.len = pkt_tmp.hdr.words32*sizeof(boost::uint32_t); + info.flags = RB_USER; + ::write(fp, NULL, 0); + + //increment index and wrap around to zero + index++; + if (index == size_t(rb_size.num_tx_frames)) index = 0; + } +} + +/*********************************************************************** + * Setup memory mapped ring buffer + **********************************************************************/ +static void setup_ring(void){ + std::cout << "setup memory mapped ring buffer... " << std::flush; + + //calculate various sizes + const size_t page_size = getpagesize(); + ioctl(fp, USRP_E_GET_RB_INFO, &rb_size); + const size_t map_size = (rb_size.num_pages_rx_flags + rb_size.num_pages_tx_flags) * page_size + + (rb_size.num_rx_frames + rb_size.num_tx_frames) * (page_size >> 1); + + //call into memory map + void *mem = ::mmap(0, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fp, 0); + if (mem == MAP_FAILED) { + std::cerr << "mmap failed" << std::endl; + std::exit(-1); + } + + //calculate the memory offsets for info and buffers + const size_t recv_info_off = 0; + const size_t recv_buff_off = recv_info_off + (rb_size.num_pages_rx_flags * page_size); + const size_t send_info_off = recv_buff_off + (rb_size.num_rx_frames * page_size/2); + const size_t send_buff_off = send_info_off + (rb_size.num_pages_tx_flags * page_size); + + //set the internal pointers for info and buffers + typedef ring_buffer_info (*rbi_pta)[]; + typedef loopback_pkt_type (*pkt_pta)[]; + char *rb_ptr = reinterpret_cast<char *>(mem); + recv_info = reinterpret_cast<rbi_pta>(rb_ptr + recv_info_off); + recv_buff = reinterpret_cast<pkt_pta>(rb_ptr + recv_buff_off); + send_info = reinterpret_cast<rbi_pta>(rb_ptr + send_info_off); + send_buff = reinterpret_cast<pkt_pta>(rb_ptr + send_buff_off); + + std::cout << "done" << std::endl; +} + +/*********************************************************************** + * Main + **********************************************************************/ +#include <boost/program_options.hpp> + +int main(int argc, char *argv[]){ + + //variables to be set by po + double duration; + size_t nwords; + + //setup the program options + namespace po = boost::program_options; + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("duration", po::value<double>(&duration)->default_value(10), "number of seconds to run loopback") + ("nwords", po::value<size_t>(&nwords)->default_value(400), "number of words32 to send per packet") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD USRP-E-Loopback %s") % desc << std::endl; + return ~0; + } + + if ((fp = ::open("/dev/usrp_e0", O_RDWR)) < 0){ + std::cerr << "Open failed" << std::endl; + return -1; + } + + //set the mode to loopback + poke16(E100_REG_MISC_XFER_RATE, (1<<8) | (1<<9)); + + //clear FIFO state in FPGA and kernel + poke32(E100_REG_CLEAR_FIFO, 0); + ::close(fp); + if ((fp = ::open("/dev/usrp_e0", O_RDWR)) < 0){ + std::cerr << "Open failed" << std::endl; + return -1; + } + + //setup the ring buffer + setup_ring(); + + //spawn threads + boost::thread_group tg; + tg.create_thread(boost::bind(&read_thread)); + tg.create_thread(boost::bind(&write_thread, nwords)); + + const boost::system_time start_time = boost::get_system_time(); + const boost::system_time finish_time = start_time + boost::posix_time::milliseconds(long(duration*1000)); + while (boost::get_system_time() < finish_time){ + boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); + std::cerr << "."; + } + running = false; + tg.join_all(); + + std::cout << std::endl; + std::cout << "seq_errors " << seq_errors << std::endl; + std::cout << "checksum_errors " << checksum_errors << std::endl; + std::cout << "sent_words32 " << sent_words32 << std::endl; + std::cout << "recvd_words32 " << recvd_words32 << std::endl; + std::cout << "approx send rate " << (sent_words32/duration)/1e6 << "Msps" << std::endl; + std::cout << "approx recv rate " << (recvd_words32/duration)/1e6 << "Msps" << std::endl; + + ::close(fp); + return seq_errors + checksum_errors; +} diff --git a/host/usrp_e_utils/usrp-e-wb-test.cpp b/host/usrp_e_utils/usrp-e-wb-test.cpp new file mode 100644 index 000000000..2b793dc06 --- /dev/null +++ b/host/usrp_e_utils/usrp-e-wb-test.cpp @@ -0,0 +1,68 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "common.hpp" +#include <cstdlib> +#include <cstdio> +#include <ctime> +#include <iostream> + +static const size_t num_test_iters = 10000000; + +int main(int, char *[]){ + + srandom(time(NULL)); //seed random() + + if ((fp = ::open("/dev/usrp_e0", O_RDWR)) < 0){ + std::cerr << "Open failed" << std::endl; + return -1; + } + + size_t num_pass = 0, num_fail = 0; + for (size_t i = 0; i < num_test_iters; i++){ + if(i%1000000 == 0) { + std::cout << "num pass: " << num_pass; + std::cout << "\tnum fail: " << num_fail << std::endl; + } + //make random values + int random_test32 = ::random(); + int random_test16 = ::random() & 0xffff; + //int random_secs = ::random(); + + //set a bunch of registers + poke16(E100_REG_MISC_TEST, random_test16); + poke32(E100_REG_SR_MISC_TEST32, random_test32); + //poke32(E100_REG_TIME64_TICKS, 0); + //poke32(E100_REG_TIME64_IMM, 1); //immediate + //poke32(E100_REG_TIME64_SECS, random_secs); + + //read a bunch of registers + if ( + (peek16(E100_REG_MISC_TEST) == random_test16) and + (peek32(E100_REG_RB_MISC_TEST32) == random_test32) and +// (peek32(E100_REG_RB_TIME_NOW_SECS) == random_secs) and +// (peek32(E100_REG_RB_TIME_NOW_TICKS) < 1000000) and + true) num_pass++; + else num_fail++; + } + + std::cout << "num pass: " << num_pass << std::endl; + std::cout << "num fail: " << num_fail << std::endl; + + ::close(fp); + return 0; +} diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt new file mode 100644 index 000000000..0ecd6b4e7 --- /dev/null +++ b/host/utils/CMakeLists.txt @@ -0,0 +1,88 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Utilities that get installed into the runtime path +######################################################################## +SET(util_runtime_sources + uhd_find_devices.cpp + uhd_usrp_probe.cpp + uhd_cal_rx_iq_balance.cpp + uhd_cal_tx_dc_offset.cpp + uhd_cal_tx_iq_balance.cpp +) + +#for each source: build an executable and install +FOREACH(util_source ${util_runtime_sources}) + GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) + ADD_EXECUTABLE(${util_name} ${util_source}) + TARGET_LINK_LIBRARIES(${util_name} uhd) + INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) +ENDFOREACH(util_source) + +######################################################################## +# Utilities that get installed into the share path +######################################################################## +SET(util_share_sources + usrp_burn_db_eeprom.cpp + usrp_burn_mb_eeprom.cpp +) + +IF(ENABLE_USB) + LIST(APPEND util_share_sources + fx2_init_eeprom.cpp + ) +ENDIF(ENABLE_USB) + +IF(LINUX AND ENABLE_USB) + INSTALL(FILES + uhd-usrp.rules + DESTINATION ${PKG_LIB_DIR}/utils + COMPONENT utilities + ) +ENDIF(LINUX AND ENABLE_USB) + +#for each source: build an executable and install +FOREACH(util_source ${util_share_sources}) + GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) + ADD_EXECUTABLE(${util_name} ${util_source}) + TARGET_LINK_LIBRARIES(${util_name} uhd) + INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +ENDFOREACH(util_source) + +IF(ENABLE_USRP2) + IF(WIN32 AND UHD_RELEASE_MODE) #include dd.exe + FILE(DOWNLOAD + "http://files.ettus.com/dd.exe" + ${CMAKE_CURRENT_BINARY_DIR}/dd.exe + ) + INSTALL(FILES + ${CMAKE_CURRENT_BINARY_DIR}/dd.exe + DESTINATION ${PKG_LIB_DIR}/utils + COMPONENT utilities + ) + ENDIF(WIN32 AND UHD_RELEASE_MODE) + INSTALL(PROGRAMS + usrp2_recovery.py + usrp2_card_burner.py + usrp2_card_burner_gui.py + usrp_n2xx_net_burner.py + usrp_n2xx_net_burner_gui.py + DESTINATION ${PKG_LIB_DIR}/utils + COMPONENT utilities + ) +ENDIF(ENABLE_USRP2) diff --git a/host/utils/fx2_init_eeprom.cpp b/host/utils/fx2_init_eeprom.cpp new file mode 100644 index 000000000..c210ae575 --- /dev/null +++ b/host/utils/fx2_init_eeprom.cpp @@ -0,0 +1,93 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <cstdlib> + +const std::string FX2_VENDOR_ID("0x04b4"); +const std::string FX2_PRODUCT_ID("0x8613"); + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string type; + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("image", po::value<std::string>(), "BIN image file") + ("vid", po::value<std::string>(), "VID of device to program") + ("pid", po::value<std::string>(), "PID of device to program") + ("type", po::value<std::string>(), "device type (usrp1 or b100)") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP EEPROM initialization %s") % desc << std::endl; + return ~0; + } + + //cant find a uninitialized usrp with this mystery module in the way... + if (std::system("/sbin/rmmod usbtest") != 0){ + std::cerr << "Did not rmmod usbtest, this may be ok..." << std::endl; + } + + //load the options into the address + uhd::device_addr_t device_addr; + device_addr["type"] = type; + if(vm.count("vid") or vm.count("pid") or vm.count("type")) { + if(not (vm.count("vid") and vm.count("pid") and vm.count("type"))) { + std::cerr << "ERROR: Must specify vid, pid, and type if specifying any of the three args" << std::endl; + } else { + device_addr["vid"] = vm["vid"].as<std::string>(); + device_addr["pid"] = vm["pid"].as<std::string>(); + device_addr["type"] = vm["type"].as<std::string>(); + } + } else { + device_addr["vid"] = FX2_VENDOR_ID; + device_addr["pid"] = FX2_PRODUCT_ID; + } + + //find and create a control transport to do the writing. + + uhd::device_addrs_t found_addrs = uhd::device::find(device_addr); + + if (found_addrs.size() == 0){ + std::cerr << "No USRP devices found" << std::endl; + return ~0; + } + + for (size_t i = 0; i < found_addrs.size(); i++){ + std::cout << "Writing EEPROM data..." << std::endl; + //uhd::device_addrs_t devs = uhd::device::find(found_addrs[i]); + uhd::device::sptr dev = uhd::device::make(found_addrs[i]); + uhd::property_tree::sptr tree = dev->get_tree(); + tree->access<std::string>("/mboards/0/load_eeprom").set(vm["image"].as<std::string>()); + } + + + std::cout << "Power-cycle the usrp for the changes to take effect." << std::endl; + return 0; +} diff --git a/host/utils/uhd-usrp.rules b/host/utils/uhd-usrp.rules new file mode 100644 index 000000000..56d9a8c43 --- /dev/null +++ b/host/utils/uhd-usrp.rules @@ -0,0 +1,19 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="fffe", ATTRS{idProduct}=="0002", MODE:="0666" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2500", ATTRS{idProduct}=="0002", MODE:="0666" diff --git a/host/utils/uhd_cal_rx_iq_balance.cpp b/host/utils/uhd_cal_rx_iq_balance.cpp new file mode 100644 index 000000000..34a0a63fe --- /dev/null +++ b/host/utils/uhd_cal_rx_iq_balance.cpp @@ -0,0 +1,240 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <cmath> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_ampl){ + uhd::set_thread_priority_safe(); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //setup variables and allocate buffer + uhd::tx_metadata_t md; + md.has_time_spec = false; + std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + + //fill buff and send until interrupted + while (not boost::this_thread::interruption_requested()){ + for (size_t i = 0; i < buff.size(); i++){ + buff[i] = float(tx_wave_ampl); + } + tx_stream->send(&buff.front(), buff.size(), md); + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double rx_lo_freq, const double tx_offset){ + //tune the receiver with no cordic + uhd::tune_request_t rx_tune_req(rx_lo_freq); + rx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + rx_tune_req.dsp_freq = 0; + usrp->set_rx_freq(rx_tune_req); + + //tune the transmitter with no cordic + uhd::tune_request_t tx_tune_req(usrp->get_rx_freq() - tx_offset); + tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + tx_tune_req.dsp_freq = 0; + usrp->set_tx_freq(tx_tune_req); + + //wait for the LOs to become locked + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + boost::system_time start = boost::get_system_time(); + while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ + if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ + throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); + } + } + + return usrp->get_rx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args; + double tx_wave_ampl, tx_offset; + double freq_start, freq_stop, freq_step; + size_t nsamps; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("verbose", "enable some verbose") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") + ("tx_offset", po::value<double>(&tx_offset)->default_value(.9344e6), "TX LO offset from the RX LO in Hz") + ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") + ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") + ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") + ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP Generate RX IQ Balance Calibration Table %s") % desc << std::endl; + std::cout << + "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" + << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //set the antennas to cal + if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ + throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); + } + usrp->set_rx_antenna("CAL"); + usrp->set_tx_antenna("CAL"); + + //set optimum defaults + set_optimum_defaults(usrp); + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //create a transmitter thread + boost::thread_group threads; + threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_ampl)); + + //re-usable buffer for samples + std::vector<samp_type> buff; + + //store the results here + std::vector<result_t> results; + + if (not vm.count("freq_start")) freq_start = usrp->get_rx_freq_range().start() + 50e6; + if (not vm.count("freq_stop")) freq_stop = usrp->get_rx_freq_range().stop() - 50e6; + + for (double rx_lo_i = freq_start; rx_lo_i <= freq_stop; rx_lo_i += freq_step){ + const double rx_lo = tune_rx_and_tx(usrp, rx_lo_i, tx_offset); + + //frequency constants for this tune event + const double actual_rx_rate = usrp->get_rx_rate(); + const double actual_tx_freq = usrp->get_tx_freq(); + const double actual_rx_freq = usrp->get_rx_freq(); + const double bb_tone_freq = actual_tx_freq - actual_rx_freq; + const double bb_imag_freq = -bb_tone_freq; + + //capture initial uncorrected value + usrp->set_rx_iq_balance(std::polar<double>(1.0, 0.0)); + capture_samples(usrp, rx_stream, buff, nsamps); + const double initial_suppression = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate) - compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + + //bounds and results from searching + std::complex<double> best_correction; + double phase_corr_start = -.3, phase_corr_stop = .3, phase_corr_step; + double ampl_corr_start = -.3, ampl_corr_stop = .3, ampl_corr_step; + double best_suppression = 0, best_phase_corr = 0, best_ampl_corr = 0; + + for (size_t i = 0; i < num_search_iters; i++){ + + phase_corr_step = (phase_corr_stop - phase_corr_start)/(num_search_steps-1); + ampl_corr_step = (ampl_corr_stop - ampl_corr_start)/(num_search_steps-1); + + for (double phase_corr = phase_corr_start; phase_corr <= phase_corr_stop + phase_corr_step/2; phase_corr += phase_corr_step){ + for (double ampl_corr = ampl_corr_start; ampl_corr <= ampl_corr_stop + ampl_corr_step/2; ampl_corr += ampl_corr_step){ + + const std::complex<double> correction = std::polar(ampl_corr+1, phase_corr*tau); + usrp->set_rx_iq_balance(correction); + + //receive some samples + capture_samples(usrp, rx_stream, buff, nsamps); + + const double tone_dbrms = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate); + const double imag_dbrms = compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + const double suppression = tone_dbrms - imag_dbrms; + + if (suppression > best_suppression){ + best_correction = correction; + best_suppression = suppression; + best_phase_corr = phase_corr; + best_ampl_corr = ampl_corr; + } + + }} + + //std::cout << "best_phase_corr " << best_phase_corr << std::endl; + //std::cout << "best_ampl_corr " << best_ampl_corr << std::endl; + //std::cout << "best_suppression " << best_suppression << std::endl; + + phase_corr_start = best_phase_corr - phase_corr_step; + phase_corr_stop = best_phase_corr + phase_corr_step; + ampl_corr_start = best_ampl_corr - ampl_corr_step; + ampl_corr_stop = best_ampl_corr + ampl_corr_step; + } + + if (best_suppression > 30){ //most likely valid, keep result + result_t result; + result.freq = rx_lo; + result.real_corr = best_correction.real(); + result.imag_corr = best_correction.imag(); + result.best = best_suppression; + result.delta = best_suppression - initial_suppression; + results.push_back(result); + if (vm.count("verbose")){ + std::cout << boost::format("RX IQ: %f MHz: best suppression %f dB, corrected %f dB") % (rx_lo/1e6) % result.best % result.delta << std::endl; + } + else std::cout << "." << std::flush; + } + + } + std::cout << std::endl; + + //stop the transmitter + threads.interrupt_all(); + threads.join_all(); + + store_results(usrp, results, "RX", "rx", "iq"); + + return 0; +} diff --git a/host/utils/uhd_cal_tx_dc_offset.cpp b/host/utils/uhd_cal_tx_dc_offset.cpp new file mode 100644 index 000000000..1b2510ba4 --- /dev/null +++ b/host/utils/uhd_cal_tx_dc_offset.cpp @@ -0,0 +1,238 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_freq, const double tx_wave_ampl){ + uhd::set_thread_priority_safe(); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //setup variables and allocate buffer + uhd::tx_metadata_t md; + md.has_time_spec = false; + std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + + //values for the wave table lookup + size_t index = 0; + const double tx_rate = usrp->get_tx_rate(); + const size_t step = boost::math::iround(wave_table_len * tx_wave_freq/tx_rate); + wave_table table(tx_wave_ampl); + + //fill buff and send until interrupted + while (not boost::this_thread::interruption_requested()){ + for (size_t i = 0; i < buff.size(); i++){ + buff[i] = table(index += step); + } + tx_stream->send(&buff.front(), buff.size(), md); + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_lo_freq, const double rx_offset){ + //tune the transmitter with no cordic + uhd::tune_request_t tx_tune_req(tx_lo_freq); + tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + tx_tune_req.dsp_freq = 0; + usrp->set_tx_freq(tx_tune_req); + + //tune the receiver + usrp->set_rx_freq(usrp->get_tx_freq() - rx_offset); + + //wait for the LOs to become locked + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + boost::system_time start = boost::get_system_time(); + while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ + if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ + throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); + } + } + + return usrp->get_tx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args; + double tx_wave_freq, tx_wave_ampl, rx_offset; + double freq_start, freq_stop, freq_step; + size_t nsamps; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("verbose", "enable some verbose") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") + ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") + ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") + ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") + ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") + ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") + ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP Generate TX DC Offset Calibration Table %s") % desc << std::endl; + std::cout << + "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" + << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //set the antennas to cal + if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ + throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); + } + usrp->set_rx_antenna("CAL"); + usrp->set_tx_antenna("CAL"); + + //set optimum defaults + set_optimum_defaults(usrp); + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //create a transmitter thread + boost::thread_group threads; + threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_freq, tx_wave_ampl)); + + //re-usable buffer for samples + std::vector<samp_type> buff; + + //store the results here + std::vector<result_t> results; + + if (not vm.count("freq_start")) freq_start = usrp->get_tx_freq_range().start() + 50e6; + if (not vm.count("freq_stop")) freq_stop = usrp->get_tx_freq_range().stop() - 50e6; + + for (double tx_lo_i = freq_start; tx_lo_i <= freq_stop; tx_lo_i += freq_step){ + const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); + + //frequency constants for this tune event + const double actual_rx_rate = usrp->get_rx_rate(); + const double actual_tx_freq = usrp->get_tx_freq(); + const double actual_rx_freq = usrp->get_rx_freq(); + const double bb_dc_freq = actual_tx_freq - actual_rx_freq; + + //capture initial uncorrected value + usrp->set_tx_dc_offset(std::complex<double>(0, 0)); + capture_samples(usrp, rx_stream, buff, nsamps); + const double initial_dc_dbrms = compute_tone_dbrms(buff, bb_dc_freq/actual_rx_rate); + + //bounds and results from searching + double dc_i_start = -.01, dc_i_stop = .01, dc_i_step; + double dc_q_start = -.01, dc_q_stop = .01, dc_q_step; + double lowest_offset = 0, best_dc_i = 0, best_dc_q = 0; + + for (size_t i = 0; i < num_search_iters; i++){ + + dc_i_step = (dc_i_stop - dc_i_start)/(num_search_steps-1); + dc_q_step = (dc_q_stop - dc_q_start)/(num_search_steps-1); + + for (double dc_i = dc_i_start; dc_i <= dc_i_stop + dc_i_step/2; dc_i += dc_i_step){ + for (double dc_q = dc_q_start; dc_q <= dc_q_stop + dc_q_step/2; dc_q += dc_q_step){ + + const std::complex<double> correction(dc_i, dc_q); + usrp->set_tx_dc_offset(correction); + + //receive some samples + capture_samples(usrp, rx_stream, buff, nsamps); + + const double dc_dbrms = compute_tone_dbrms(buff, bb_dc_freq/actual_rx_rate); + + if (dc_dbrms < lowest_offset){ + lowest_offset = dc_dbrms; + best_dc_i = dc_i; + best_dc_q = dc_q; + } + + }} + + //std::cout << "best_dc_i " << best_dc_i << std::endl; + //std::cout << "best_dc_q " << best_dc_q << std::endl; + //std::cout << "lowest_offset " << lowest_offset << std::endl; + + dc_i_start = best_dc_i - dc_i_step; + dc_i_stop = best_dc_i + dc_i_step; + dc_q_start = best_dc_q - dc_q_step; + dc_q_stop = best_dc_q + dc_q_step; + } + + if (lowest_offset < initial_dc_dbrms){ //most likely valid, keep result + result_t result; + result.freq = tx_lo; + result.real_corr = best_dc_i; + result.imag_corr = best_dc_q; + result.best = lowest_offset; + result.delta = initial_dc_dbrms - lowest_offset; + results.push_back(result); + if (vm.count("verbose")){ + std::cout << boost::format("TX DC: %f MHz: lowest offset %f dB, corrected %f dB") % (tx_lo/1e6) % result.best % result.delta << std::endl; + } + else std::cout << "." << std::flush; + } + + } + std::cout << std::endl; + + //stop the transmitter + threads.interrupt_all(); + threads.join_all(); + + store_results(usrp, results, "TX", "tx", "dc"); + + return 0; +} diff --git a/host/utils/uhd_cal_tx_iq_balance.cpp b/host/utils/uhd_cal_tx_iq_balance.cpp new file mode 100644 index 000000000..c2222e777 --- /dev/null +++ b/host/utils/uhd_cal_tx_iq_balance.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_freq, const double tx_wave_ampl){ + uhd::set_thread_priority_safe(); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //setup variables and allocate buffer + uhd::tx_metadata_t md; + md.has_time_spec = false; + std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + + //values for the wave table lookup + size_t index = 0; + const double tx_rate = usrp->get_tx_rate(); + const size_t step = boost::math::iround(wave_table_len * tx_wave_freq/tx_rate); + wave_table table(tx_wave_ampl); + + //fill buff and send until interrupted + while (not boost::this_thread::interruption_requested()){ + for (size_t i = 0; i < buff.size(); i++){ + buff[i] = table(index += step); + } + tx_stream->send(&buff.front(), buff.size(), md); + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_lo_freq, const double rx_offset){ + //tune the transmitter with no cordic + uhd::tune_request_t tx_tune_req(tx_lo_freq); + tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + tx_tune_req.dsp_freq = 0; + usrp->set_tx_freq(tx_tune_req); + + //tune the receiver + usrp->set_rx_freq(usrp->get_tx_freq() - rx_offset); + + //wait for the LOs to become locked + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + boost::system_time start = boost::get_system_time(); + while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ + if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ + throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); + } + } + + return usrp->get_tx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args; + double tx_wave_freq, tx_wave_ampl, rx_offset; + double freq_start, freq_stop, freq_step; + size_t nsamps; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("verbose", "enable some verbose") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") + ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") + ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") + ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") + ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") + ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") + ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP Generate TX IQ Balance Calibration Table %s") % desc << std::endl; + std::cout << + "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" + << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //set the antennas to cal + if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ + throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); + } + usrp->set_rx_antenna("CAL"); + usrp->set_tx_antenna("CAL"); + + //set optimum defaults + set_optimum_defaults(usrp); + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //create a transmitter thread + boost::thread_group threads; + threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_freq, tx_wave_ampl)); + + //re-usable buffer for samples + std::vector<samp_type> buff; + + //store the results here + std::vector<result_t> results; + + if (not vm.count("freq_start")) freq_start = usrp->get_tx_freq_range().start() + 50e6; + if (not vm.count("freq_stop")) freq_stop = usrp->get_tx_freq_range().stop() - 50e6; + + for (double tx_lo_i = freq_start; tx_lo_i <= freq_stop; tx_lo_i += freq_step){ + const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); + + //frequency constants for this tune event + const double actual_rx_rate = usrp->get_rx_rate(); + const double actual_tx_freq = usrp->get_tx_freq(); + const double actual_rx_freq = usrp->get_rx_freq(); + const double bb_tone_freq = actual_tx_freq + tx_wave_freq - actual_rx_freq; + const double bb_imag_freq = actual_tx_freq - tx_wave_freq - actual_rx_freq; + + //capture initial uncorrected value + usrp->set_tx_iq_balance(std::polar<double>(1.0, 0.0)); + capture_samples(usrp, rx_stream, buff, nsamps); + const double initial_suppression = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate) - compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + + //bounds and results from searching + std::complex<double> best_correction; + double phase_corr_start = -.3, phase_corr_stop = .3, phase_corr_step; + double ampl_corr_start = -.3, ampl_corr_stop = .3, ampl_corr_step; + double best_suppression = 0, best_phase_corr = 0, best_ampl_corr = 0; + + for (size_t i = 0; i < num_search_iters; i++){ + + phase_corr_step = (phase_corr_stop - phase_corr_start)/(num_search_steps-1); + ampl_corr_step = (ampl_corr_stop - ampl_corr_start)/(num_search_steps-1); + + for (double phase_corr = phase_corr_start; phase_corr <= phase_corr_stop + phase_corr_step/2; phase_corr += phase_corr_step){ + for (double ampl_corr = ampl_corr_start; ampl_corr <= ampl_corr_stop + ampl_corr_step/2; ampl_corr += ampl_corr_step){ + + const std::complex<double> correction = std::polar(ampl_corr+1, phase_corr*tau); + usrp->set_tx_iq_balance(correction); + + //receive some samples + capture_samples(usrp, rx_stream, buff, nsamps); + + const double tone_dbrms = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate); + const double imag_dbrms = compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + const double suppression = tone_dbrms - imag_dbrms; + + if (suppression > best_suppression){ + best_correction = correction; + best_suppression = suppression; + best_phase_corr = phase_corr; + best_ampl_corr = ampl_corr; + } + + }} + + //std::cout << "best_phase_corr " << best_phase_corr << std::endl; + //std::cout << "best_ampl_corr " << best_ampl_corr << std::endl; + //std::cout << "best_suppression " << best_suppression << std::endl; + + phase_corr_start = best_phase_corr - phase_corr_step; + phase_corr_stop = best_phase_corr + phase_corr_step; + ampl_corr_start = best_ampl_corr - ampl_corr_step; + ampl_corr_stop = best_ampl_corr + ampl_corr_step; + } + + if (best_suppression > 30){ //most likely valid, keep result + result_t result; + result.freq = tx_lo; + result.real_corr = best_correction.real(); + result.imag_corr = best_correction.imag(); + result.best = best_suppression; + result.delta = best_suppression - initial_suppression; + results.push_back(result); + if (vm.count("verbose")){ + std::cout << boost::format("TX IQ: %f MHz: best suppression %f dB, corrected %f dB") % (tx_lo/1e6) % result.best % result.delta << std::endl; + } + else std::cout << "." << std::flush; + } + + } + std::cout << std::endl; + + //stop the transmitter + threads.interrupt_all(); + threads.join_all(); + + store_results(usrp, results, "TX", "tx", "iq"); + + return 0; +} diff --git a/host/utils/uhd_find_devices.cpp b/host/utils/uhd_find_devices.cpp new file mode 100644 index 000000000..b778eeb68 --- /dev/null +++ b/host/utils/uhd_find_devices.cpp @@ -0,0 +1,60 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>()->default_value(""), "device address args") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD Find Devices %s") % desc << std::endl; + return ~0; + } + + //discover the usrps and print the results + uhd::device_addrs_t device_addrs = uhd::device::find(vm["args"].as<std::string>()); + + if (device_addrs.size() == 0){ + std::cerr << "No UHD Devices Found" << std::endl; + return ~0; + } + + for (size_t i = 0; i < device_addrs.size(); i++){ + std::cout << "--------------------------------------------------" << std::endl; + std::cout << "-- UHD Device " << i << std::endl; + std::cout << "--------------------------------------------------" << std::endl; + std::cout << device_addrs[i].to_pp_string() << std::endl << std::endl; + //uhd::device::make(device_addrs[i]); //test make + } + + return 0; +} diff --git a/host/utils/uhd_usrp_probe.cpp b/host/utils/uhd_usrp_probe.cpp new file mode 100644 index 000000000..1bd49a5ff --- /dev/null +++ b/host/utils/uhd_usrp_probe.cpp @@ -0,0 +1,214 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/version.hpp> +#include <uhd/device.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/property_tree.hpp> +#include <boost/algorithm/string.hpp> //for split +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <iostream> +#include <sstream> +#include <vector> + +namespace po = boost::program_options; +using namespace uhd; + +static std::string indent(size_t level){ + return (level)? (indent(level-1) + " ") : ""; +} + +static std::string make_border(const std::string &text){ + std::stringstream ss; + ss << boost::format(" _____________________________________________________") << std::endl; + ss << boost::format(" /") << std::endl; + std::vector<std::string> lines; boost::split(lines, text, boost::is_any_of("\n")); + while (lines.back().empty()) lines.pop_back(); //strip trailing newlines + if (lines.size()) lines[0] = " " + lines[0]; //indent the title line + BOOST_FOREACH(const std::string &line, lines){ + ss << boost::format("| %s") % line << std::endl; + } + //ss << boost::format(" \\_____________________________________________________") << std::endl; + return ss.str(); +} + +static std::string get_dsp_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("%s DSP: %s") % type % path.leaf() << std::endl; + //ss << std::endl; + meta_range_t freq_range = tree->access<meta_range_t>(path / "freq/range").get(); + ss << boost::format("Freq range: %.3f to %.3f Mhz") % (freq_range.start()/1e6) % (freq_range.stop()/1e6) << std::endl;; + return ss.str(); +} + +static std::string prop_names_to_pp_string(const std::vector<std::string> &prop_names){ + std::stringstream ss; size_t count = 0; + BOOST_FOREACH(const std::string &prop_name, prop_names){ + ss << ((count++)? ", " : "") << prop_name; + } + return ss.str(); +} + +static std::string get_subdev_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("%s Subdev: %s") % type % path.leaf() << std::endl; + //ss << std::endl; + + ss << boost::format("Name: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; + ss << boost::format("Antennas: %s") % prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "antenna/options").get()) << std::endl; + ss << boost::format("Sensors: %s") % prop_names_to_pp_string(tree->list(path / "sensors")) << std::endl; + + meta_range_t freq_range = tree->access<meta_range_t>(path / "freq/range").get(); + ss << boost::format("Freq range: %.3f to %.3f Mhz") % (freq_range.start()/1e6) % (freq_range.stop()/1e6) << std::endl; + + std::vector<std::string> gain_names = tree->list(path / "gains"); + if (gain_names.size() == 0) ss << "Gain Elements: None" << std::endl; + BOOST_FOREACH(const std::string &name, gain_names){ + meta_range_t gain_range = tree->access<meta_range_t>(path / "gains" / name / "range").get(); + ss << boost::format("Gain range %s: %.1f to %.1f step %.1f dB") % name % gain_range.start() % gain_range.stop() % gain_range.step() << std::endl; + } + + ss << boost::format("Connection Type: %s") % (tree->access<std::string>(path / "connection").get()) << std::endl; + ss << boost::format("Uses LO offset: %s") % ((tree->access<bool>(path / "use_lo_offset").get())? "Yes" : "No") << std::endl; + + return ss.str(); +} + +static std::string get_codec_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("%s Codec: %s") % type % path.leaf() << std::endl; + //ss << std::endl; + + ss << boost::format("Name: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; + std::vector<std::string> gain_names = tree->list(path / "gains"); + if (gain_names.size() == 0) ss << "Gain Elements: None" << std::endl; + BOOST_FOREACH(const std::string &name, gain_names){ + meta_range_t gain_range = tree->access<meta_range_t>(path / "gains" / name / "range").get(); + ss << boost::format("Gain range %s: %.1f to %.1f step %.1f dB") % name % gain_range.start() % gain_range.stop() % gain_range.step() << std::endl; + } + return ss.str(); +} + +static std::string get_dboard_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("%s Dboard: %s") % type % path.leaf() << std::endl; + //ss << std::endl; + const std::string prefix = (type == "RX")? "rx" : "tx"; + usrp::dboard_eeprom_t db_eeprom = tree->access<usrp::dboard_eeprom_t>(path / (prefix + "_eeprom")).get(); + if (db_eeprom.id != usrp::dboard_id_t::none()) ss << boost::format("ID: %s") % db_eeprom.id.to_pp_string() << std::endl; + if (not db_eeprom.serial.empty()) ss << boost::format("Serial: %s") % db_eeprom.serial << std::endl; + if (type == "TX"){ + usrp::dboard_eeprom_t gdb_eeprom = tree->access<usrp::dboard_eeprom_t>(path / "gdb_eeprom").get(); + if (gdb_eeprom.id != usrp::dboard_id_t::none()) ss << boost::format("ID: %s") % gdb_eeprom.id.to_pp_string() << std::endl; + if (not gdb_eeprom.serial.empty()) ss << boost::format("Serial: %s") % gdb_eeprom.serial << std::endl; + } + BOOST_FOREACH(const std::string &name, tree->list(path / (prefix + "_frontends"))){ + ss << make_border(get_subdev_pp_string(type, tree, path / (prefix + "_frontends") / name)); + } + ss << make_border(get_codec_pp_string(type, tree, path.branch_path().branch_path() / (prefix + "_codecs") / path.leaf())); + return ss.str(); +} + +static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("Mboard: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; + //ss << std::endl; + usrp::mboard_eeprom_t mb_eeprom = tree->access<usrp::mboard_eeprom_t>(path / "eeprom").get(); + BOOST_FOREACH(const std::string &key, mb_eeprom.keys()){ + if (not mb_eeprom[key].empty()) ss << boost::format("%s: %s") % key % mb_eeprom[key] << std::endl; + } + ss << std::endl; + ss << "Time sources: " << prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "time_source" / "options").get()) << std::endl; + ss << "Clock sources: " << prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "clock_source" / "options").get()) << std::endl; + ss << "Sensors: " << prop_names_to_pp_string(tree->list(path / "sensors")) << std::endl; + BOOST_FOREACH(const std::string &name, tree->list(path / "rx_dsps")){ + ss << make_border(get_dsp_pp_string("RX", tree, path / "rx_dsps" / name)); + } + BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ + ss << make_border(get_dboard_pp_string("RX", tree, path / "dboards" / name)); + } + BOOST_FOREACH(const std::string &name, tree->list(path / "tx_dsps")){ + ss << make_border(get_dsp_pp_string("TX", tree, path / "tx_dsps" / name)); + } + BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ + ss << make_border(get_dboard_pp_string("TX", tree, path / "dboards" / name)); + } + return ss.str(); +} + + +static std::string get_device_pp_string(property_tree::sptr tree){ + std::stringstream ss; + ss << boost::format("Device: %s") % (tree->access<std::string>("/name").get()) << std::endl; + //ss << std::endl; + BOOST_FOREACH(const std::string &name, tree->list("/mboards")){ + ss << make_border(get_mboard_pp_string(tree, "/mboards/" + name)); + } + return ss.str(); +} + +void print_tree(const uhd::fs_path &path, uhd::property_tree::sptr tree){ + std::cout << path << std::endl; + BOOST_FOREACH(const std::string &name, tree->list(path)){ + print_tree(path / name, tree); + } +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("version", "print the version string and exit") + ("args", po::value<std::string>()->default_value(""), "device address args") + ("tree", "specify to print a complete property tree") + ("string", po::value<std::string>(), "query a string value from the properties tree") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD USRP Probe %s") % desc << std::endl; + return ~0; + } + + if (vm.count("version")){ + std::cout << uhd::get_version_string() << std::endl; + return 0; + } + + device::sptr dev = device::make(vm["args"].as<std::string>()); + property_tree::sptr tree = dev->get_tree(); + + if (vm.count("string")){ + std::cout << tree->access<std::string>(vm["string"].as<std::string>()).get() << std::endl; + return 0; + } + + if (vm.count("tree") != 0) print_tree("/", tree); + else std::cout << make_border(get_device_pp_string(tree)) << std::endl; + + return 0; +} diff --git a/host/utils/usrp2_card_burner.py b/host/utils/usrp2_card_burner.py new file mode 100755 index 000000000..34c40d5a0 --- /dev/null +++ b/host/utils/usrp2_card_burner.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import platform +import tempfile +import subprocess +try: + import urllib.request +except ImportError: + import urllib + urllib.request = urllib +import optparse +import math +import os +import re + +######################################################################## +# constants +######################################################################## +SECTOR_SIZE = 512 # bytes +MAX_FILE_SIZE = 1 * (2**20) # maximum number of bytes we'll burn to a slot + +FPGA_OFFSET = 0 # offset in flash to fpga image +FIRMWARE_OFFSET = 1 * (2**20) # offset in flash to firmware image + +MAX_SD_CARD_SIZE = 2048e6 # bytes (any bigger is sdhc) + +######################################################################## +# helper functions +######################################################################## +def command(*args): + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + ret = p.wait() + verbose = p.stdout.read().decode() + if ret != 0: raise Exception(verbose) + return verbose + +def get_dd_path(): + if platform.system() == 'Windows': + dd_path = os.path.join(os.path.dirname(__file__), 'dd.exe') + if os.path.exists(dd_path): return dd_path + dd_path = os.path.join(tempfile.gettempdir(), 'dd.exe') + if not os.path.exists(dd_path): + print('Downloading dd.exe to %s'%dd_path) + dd_bin = urllib.request.urlopen('http://files.ettus.com/dd.exe').read() + open(dd_path, 'wb').write(dd_bin) + return dd_path + return 'dd' + +def int_ceil_div(num, den): + return int(math.ceil(float(num)/float(den))) + +def get_tmp_file(): + tmp = tempfile.mkstemp() + os.close(tmp[0]) + return tmp[1] + +######################################################################## +# list possible devices +######################################################################## +def get_raw_device_hints(): + #################################################################### + # Platform Windows: parse the output of dd.exe --list + #################################################################### + if platform.system() == 'Windows': + def extract_info_value(info, key): + return info.split(key)[-1].split()[0] + def get_info_list(output): + in_info = False + for line in output.splitlines(): + if line.startswith('\\\\'): in_info = True; info = '' + elif in_info and not line.strip(): in_info = False; yield info + if in_info: info += '\n'+line.strip() + def is_info_valid(info): + try: + if 'link to' not in info: return False + #handles two spellings of remov(e)able: + if 'remov' not in info.lower(): return False + if 'size is' in info and int(extract_info_value(info, 'size is')) > MAX_SD_CARD_SIZE: return False + except: return False + return True + def extract_info_name(info): + for key in ('Mounted on', 'link to'): + if key in info: return extract_info_value(info, key) + return info.splitlines()[0].strip() + + return sorted(set(map(extract_info_name, list(filter(is_info_valid, get_info_list(command(get_dd_path(), '--list'))))))) + + #################################################################### + # Platform Linux: parse procfs /proc/partitions + #################################################################### + if platform.system() == 'Linux': + devs = list() + for line in command('cat', '/proc/partitions').splitlines(): + try: + major, minor, blocks, name = line.split() + if not name[-1].isdigit() and int(minor) == 0: continue + if int(blocks)*1024 > MAX_SD_CARD_SIZE: continue + except: continue + devs.append(os.path.join('/dev', name)) + + return sorted(set(devs)) + + #################################################################### + # Platform Mac OS X: parse diskutil list and info commands + #################################################################### + if platform.system() == 'Darwin': + devs = [d.split()[0] for d in [l for l in command('diskutil', 'list').splitlines() if l.startswith('/dev')]] + def output_to_info(output): + return dict([list(map(lambda x: x.strip(), pair.lower().split(':'))) for pair in [l for l in output.splitlines() if ':' in l]]) + def is_dev_valid(dev): + info = output_to_info(command('diskutil', 'info', dev)) + try: + if 'internal' in info and info['internal'] == 'yes': return False + if 'ejectable' in info and info['ejectable'] == 'no': return False + if 'total size' in info: + size_match = re.match('^.*\((\d+)\s*bytes\).*$', info['total size']) + if size_match and int(size_match.groups()[0]) > MAX_SD_CARD_SIZE: return False + except: return False + return True + + return sorted(set(filter(is_dev_valid, devs))) + + #################################################################### + # Platform Others: + #################################################################### + return () + +######################################################################## +# write and verify with dd +######################################################################## +def verify_image(image_file, device_file, offset): + #create a temporary file to store the readback image + tmp_file = get_tmp_file() + + #read the image data + img_data = open(image_file, 'rb').read() + count = int_ceil_div(len(img_data), SECTOR_SIZE) + + #execute a dd subprocess + verbose = command( + get_dd_path(), + "of=%s"%tmp_file, + "if=%s"%device_file, + "skip=%d"%(offset/SECTOR_SIZE), + "bs=%d"%SECTOR_SIZE, + "count=%d"%count, + ) + + #verfy the data + tmp_data = open(tmp_file, 'rb').read(len(img_data)) + if img_data != tmp_data: return 'Verification Failed:\n%s'%verbose + return 'Verification Passed:\n%s'%verbose + +def write_image(image_file, device_file, offset): + #create a temporary file to store the padded image + tmp_file = get_tmp_file() + + #write the padded image data + img_data = open(image_file, 'rb').read() + count = int_ceil_div(len(img_data), SECTOR_SIZE) + pad_len = SECTOR_SIZE*count - len(img_data) + padding = bytes(b'\x00')*pad_len #zero-padding + open(tmp_file, 'wb').write(img_data + padding) + + #execute a dd subprocess + verbose = command( + get_dd_path(), + "if=%s"%tmp_file, + "of=%s"%device_file, + "seek=%d"%(offset/SECTOR_SIZE), + "bs=%d"%SECTOR_SIZE, + "count=%d"%count, + ) + + try: #exec the sync command (only works on linux) + if platform.system() == 'Linux': command('sync') + except: pass + + return verbose + +def write_and_verify(image_file, device_file, offset): + if os.path.getsize(image_file) > MAX_FILE_SIZE: + raise Exception('Image file larger than %d bytes!'%MAX_FILE_SIZE) + return '%s\n%s'%( + write_image( + image_file=image_file, + device_file=device_file, + offset=offset, + ), verify_image( + image_file=image_file, + device_file=device_file, + offset=offset, + ), + ) + +def burn_sd_card(dev, fw, fpga): + verbose = '' + if fw: verbose += 'Burn firmware image:\n%s\n'%write_and_verify( + image_file=fw, device_file=dev, offset=FIRMWARE_OFFSET + ) + if fpga: verbose += 'Burn fpga image:\n%s\n'%write_and_verify( + image_file=fpga, device_file=dev, offset=FPGA_OFFSET + ) + return verbose + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = optparse.OptionParser() + parser.add_option("--dev", type="string", help="raw device path", default='') + parser.add_option("--fw", type="string", help="firmware image path (optional)", default='') + parser.add_option("--fpga", type="string", help="fpga image path (optional)", default='') + parser.add_option("--list", action="store_true", help="list possible raw devices", default=False) + parser.add_option("--force", action="store_true", help="override safety check", default=False) + (options, args) = parser.parse_args() + + return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + device_hints = get_raw_device_hints() + show_listing = options.list + + if not show_listing and not options.force and options.dev and options.dev not in device_hints: + print('The device "%s" was not in the list of possible raw devices.'%options.dev) + print('The card burner application will now exit without burning your card.') + print('To override this safety check, specify the --force option.\n') + show_listing = True + + if show_listing: + print('Possible raw devices:') + print(' ' + '\n '.join(device_hints)) + exit() + + if not options.dev: raise Exception('no raw device path specified') + print(burn_sd_card(dev=options.dev, fw=options.fw, fpga=options.fpga)) diff --git a/host/utils/usrp2_card_burner_gui.py b/host/utils/usrp2_card_burner_gui.py new file mode 100755 index 000000000..2941629b9 --- /dev/null +++ b/host/utils/usrp2_card_burner_gui.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import usrp2_card_burner #import implementation +try: + import tkinter, tkinter.filedialog, tkinter.font, tkinter.messagebox +except ImportError: + import tkFileDialog, tkFont, tkMessageBox + import Tkinter as tkinter + tkinter.filedialog = tkFileDialog + tkinter.font = tkFont + tkinter.messagebox = tkMessageBox +import os + +class BinFileEntry(tkinter.Frame): + """ + Simple file entry widget for getting the file path of bin files. + Combines a label, entry, and button with file dialog callback. + """ + + def __init__(self, root, what, def_path=''): + self._what = what + tkinter.Frame.__init__(self, root) + tkinter.Label(self, text=what+":").pack(side=tkinter.LEFT) + self._entry = tkinter.Entry(self, width=50) + self._entry.insert(tkinter.END, def_path) + self._entry.pack(side=tkinter.LEFT) + tkinter.Button(self, text="...", command=self._button_cb).pack(side=tkinter.LEFT) + + def _button_cb(self): + filename = tkinter.filedialog.askopenfilename( + parent=self, + filetypes=[('bin files', '*.bin'), ('all files', '*.*')], + title="Select bin file for %s"%self._what, + initialdir=os.path.dirname(self.get_filename()), + ) + + # open file on your own + if filename: + self._entry.delete(0, tkinter.END) + self._entry.insert(0, filename) + + def get_filename(self): + return self._entry.get() + +class DeviceEntryWidget(tkinter.Frame): + """ + Simple entry widget for getting the raw device name. + Combines a label, entry, and helpful text box with hints. + """ + + def __init__(self, root, text=''): + tkinter.Frame.__init__(self, root) + + tkinter.Button(self, text="Rescan for Devices", command=self._reload_cb).pack() + + self._hints = tkinter.Listbox(self) + self._hints.bind("<<ListboxSelect>>", self._listbox_cb) + self._reload_cb() + self._hints.pack(expand=tkinter.YES, fill=tkinter.X) + + frame = tkinter.Frame(self) + frame.pack() + + tkinter.Label(frame, text="Raw Device:").pack(side=tkinter.LEFT) + self._entry = tkinter.Entry(frame, width=50) + self._entry.insert(tkinter.END, text) + self._entry.pack(side=tkinter.LEFT) + + def _reload_cb(self): + self._hints.delete(0, tkinter.END) + for hint in usrp2_card_burner.get_raw_device_hints(): + self._hints.insert(tkinter.END, hint) + + def _listbox_cb(self, event): + try: + sel = self._hints.get(self._hints.curselection()[0]) + self._entry.delete(0, tkinter.END) + self._entry.insert(0, sel) + except Exception as e: print(e) + + def get_devname(self): + return self._entry.get() + +class SectionLabel(tkinter.Label): + """ + Make a text label with bold font. + """ + + def __init__(self, root, text): + tkinter.Label.__init__(self, root, text=text) + + #set the font bold + f = tkinter.font.Font(font=self['font']) + f['weight'] = 'bold' + self['font'] = f.name + +class USRP2CardBurnerApp(tkinter.Frame): + """ + The top level gui application for the usrp2 sd card burner. + Creates entry widgets and button with callback to write images. + """ + + def __init__(self, root, dev, fw, fpga): + + tkinter.Frame.__init__(self, root) + + #pack the file entry widgets + SectionLabel(self, text="Select Images").pack(pady=5) + self._fw_img_entry = BinFileEntry(self, "Firmware Image", def_path=fw) + self._fw_img_entry.pack() + self._fpga_img_entry = BinFileEntry(self, "FPGA Image", def_path=fpga) + self._fpga_img_entry.pack() + + #pack the destination entry widget + SectionLabel(self, text="Select Device").pack(pady=5) + self._raw_dev_entry = DeviceEntryWidget(self, text=dev) + self._raw_dev_entry.pack() + + #the do it button + SectionLabel(self, text="").pack(pady=5) + tkinter.Label(self, text="Warning! This tool can overwrite your hard drive. Use with caution.").pack() + tkinter.Button(self, text="Burn SD Card", command=self._burn).pack() + + def _burn(self): + #grab strings from the gui + fw = self._fw_img_entry.get_filename() + fpga = self._fpga_img_entry.get_filename() + dev = self._raw_dev_entry.get_devname() + + #check input + if not dev: + tkinter.messagebox.showerror('Error:', 'No device specified!') + return + if not fw and not fpga: + tkinter.messagebox.showerror('Error:', 'No images specified!') + return + if fw and not os.path.exists(fw): + tkinter.messagebox.showerror('Error:', 'Firmware image not found!') + return + if fpga and not os.path.exists(fpga): + tkinter.messagebox.showerror('Error:', 'FPGA image not found!') + return + + #burn the sd card + try: + verbose = usrp2_card_burner.burn_sd_card(dev=dev, fw=fw, fpga=fpga) + tkinter.messagebox.showinfo('Verbose:', verbose) + except Exception as e: + tkinter.messagebox.showerror('Verbose:', 'Error: %s'%str(e)) + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = usrp2_card_burner.get_options() + root = tkinter.Tk() + root.title('USRP2 SD Card Burner') + USRP2CardBurnerApp(root, dev=options.dev, fw=options.fw, fpga=options.fpga).pack() + root.mainloop() + exit() diff --git a/host/utils/usrp2_recovery.py b/host/utils/usrp2_recovery.py new file mode 100755 index 000000000..c7578d3a0 --- /dev/null +++ b/host/utils/usrp2_recovery.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +""" +The usrp2 recovery app: + +When the usrp2 has an unknown or bad ip address in its eeprom, +it may not be possible to communicate with the usrp2 over ip/udp. + +This app will send a raw ethernet packet to bypass the ip layer. +The packet will contain a known ip address to burn into eeprom. +Because the recovery packet is sent with a broadcast mac address, +only one usrp2 should be present on the interface upon execution. + +This app requires super-user privileges and only works on linux. +""" + +import socket +import struct +import optparse + +BCAST_MAC_ADDR = 'ff:ff:ff:ff:ff:ff' +RECOVERY_ETHERTYPE = 0xbeee +IP_RECOVERY_CODE = 'addr' + +def mac_addr_repr_to_binary_string(mac_addr): + return ''.join([chr(int(x, 16)) for x in mac_addr.split(':')]) + +if __name__ == '__main__': + parser = optparse.OptionParser(usage='usage: %prog [options]\n'+__doc__) + parser.add_option('--ifc', type='string', help='ethernet interface name [default=%default]', default='eth0') + parser.add_option('--new-ip', type='string', help='ip address to set [default=%default]', default='192.168.10.2') + (options, args) = parser.parse_args() + + #create the raw socket + print("Opening raw socket on interface:", options.ifc) + soc = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) + soc.bind((options.ifc, RECOVERY_ETHERTYPE)) + + #create the recovery packet + print("Loading packet with ip address:", options.new_ip) + packet = struct.pack( + '!6s6sH4s4s', + mac_addr_repr_to_binary_string(BCAST_MAC_ADDR), + mac_addr_repr_to_binary_string(BCAST_MAC_ADDR), + RECOVERY_ETHERTYPE, + IP_RECOVERY_CODE, + socket.inet_aton(options.new_ip), + ) + + print("Sending packet (%d bytes)"%len(packet)) + soc.send(packet) + print("Done") diff --git a/host/utils/usrp_burn_db_eeprom.cpp b/host/utils/usrp_burn_db_eeprom.cpp new file mode 100644 index 000000000..b6b2dc4d6 --- /dev/null +++ b/host/utils/usrp_burn_db_eeprom.cpp @@ -0,0 +1,100 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/assign.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + //command line variables + std::string args, slot, unit; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("slot", po::value<std::string>(&slot)->default_value(""), "dboard slot name [default is blank for automatic]") + ("unit", po::value<std::string>(&unit)->default_value(""), "which unit [RX, TX, or GDB]") + ("id", po::value<std::string>(), "dboard id to burn, omit for readback") + ("ser", po::value<std::string>(), "serial to burn, omit for readback") + ("rev", po::value<std::string>(), "revision to burn, omit for readback") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP Burn Daughterboard EEPROM %s") % desc << std::endl; + std::cout << boost::format( + "Omit the ID argument to perform readback,\n" + "Or specify a new ID to burn into the EEPROM.\n" + ) << std::endl; + return ~0; + } + + //make the device and extract the dboard w/ property + device::sptr dev = device::make(args); + uhd::property_tree::sptr tree = dev->get_tree(); + const uhd::fs_path db_root = "/mboards/0/dboards"; + std::vector<std::string> dboard_names = tree->list(db_root); + if (dboard_names.size() == 1 and slot.empty()) slot = dboard_names.front(); + uhd::assert_has(dboard_names, slot, "dboard slot name"); + + std::cout << boost::format("Reading %s EEPROM on %s dboard...") % unit % slot << std::endl; + boost::to_lower(unit); + const uhd::fs_path db_path = db_root / slot / (unit + "_eeprom"); + dboard_eeprom_t db_eeprom = tree->access<dboard_eeprom_t>(db_path).get(); + + //------------- handle the dboard ID -----------------------------// + if (vm.count("id")){ + db_eeprom.id = dboard_id_t::from_string(vm["id"].as<std::string>()); + tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); + } + std::cout << boost::format(" Current ID: %s") % db_eeprom.id.to_pp_string() << std::endl; + + //------------- handle the dboard serial--------------------------// + if (vm.count("ser")){ + db_eeprom.serial = vm["ser"].as<std::string>(); + tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); + } + std::cout << boost::format(" Current serial: \"%s\"") % db_eeprom.serial << std::endl; + + //------------- handle the dboard revision------------------------// + if (vm.count("rev")){ + db_eeprom.revision = vm["rev"].as<std::string>(); + tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); + } + std::cout << boost::format(" Current revision: \"%s\"") % db_eeprom.revision << std::endl; + + std::cout << " Done" << std::endl << std::endl; + return 0; +} diff --git a/host/utils/usrp_burn_mb_eeprom.cpp b/host/utils/usrp_burn_mb_eeprom.cpp new file mode 100644 index 000000000..ca9a6c8ba --- /dev/null +++ b/host/utils/usrp_burn_mb_eeprom.cpp @@ -0,0 +1,78 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args, key, val; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("key", po::value<std::string>(&key), "the indentifier for a value in EEPROM") + ("val", po::value<std::string>(&val), "the new value to set, omit for readback") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help") or not vm.count("key")){ + std::cout << boost::format("USRP Burn Motherboard EEPROM %s") % desc << std::endl; + std::cout << boost::format( + "Omit the value argument to perform a readback,\n" + "Or specify a new value to burn into the EEPROM.\n" + ) << std::endl; + return ~0; + } + + std::cout << "Creating USRP device from address: " + args << std::endl; + uhd::device::sptr dev = uhd::device::make(args); + uhd::property_tree::sptr tree = dev->get_tree(); + std::cout << std::endl; + + if (true /*always readback*/){ + std::cout << "Fetching current settings from EEPROM..." << std::endl; + uhd::usrp::mboard_eeprom_t mb_eeprom = tree->access<uhd::usrp::mboard_eeprom_t>("/mboards/0/eeprom").get(); + if (not mb_eeprom.has_key(key)){ + std::cerr << boost::format("Cannot find value for EEPROM[%s]") % key << std::endl; + return ~0; + } + std::cout << boost::format(" EEPROM [\"%s\"] is \"%s\"") % key % mb_eeprom[key] << std::endl; + std::cout << std::endl; + } + if (vm.count("val")){ + uhd::usrp::mboard_eeprom_t mb_eeprom; mb_eeprom[key] = val; + std::cout << boost::format("Setting EEPROM [\"%s\"] to \"%s\"...") % key % val << std::endl; + tree->access<uhd::usrp::mboard_eeprom_t>("/mboards/0/eeprom").set(mb_eeprom); + std::cout << "Power-cycle the USRP device for the changes to take effect." << std::endl; + std::cout << std::endl; + } + + std::cout << "Done" << std::endl; + return 0; +} diff --git a/host/utils/usrp_cal_utils.hpp b/host/utils/usrp_cal_utils.hpp new file mode 100644 index 000000000..6d0c500e3 --- /dev/null +++ b/host/utils/usrp_cal_utils.hpp @@ -0,0 +1,217 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/paths.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/filesystem.hpp> +#include <iostream> +#include <vector> +#include <complex> +#include <cmath> +#include <fstream> + +namespace fs = boost::filesystem; + +struct result_t{double freq, real_corr, imag_corr, best, delta;}; + +typedef std::complex<float> samp_type; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const double tau = 6.28318531; +static const size_t wave_table_len = 8192; +static const size_t num_search_steps = 5; +static const size_t num_search_iters = 7; +static const double default_freq_step = 7.3e6; +static const size_t default_num_samps = 10000; + +/*********************************************************************** + * Set standard defaults for devices + **********************************************************************/ +static inline void set_optimum_defaults(uhd::usrp::multi_usrp::sptr usrp){ + uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); + + const uhd::fs_path mb_path = "/mboards/0"; + const std::string mb_name = tree->access<std::string>(mb_path / "name").get(); + if (mb_name.find("USRP2") != std::string::npos or mb_name.find("N200") != std::string::npos or mb_name.find("N210") != std::string::npos){ + usrp->set_tx_rate(12.5e6); + usrp->set_rx_rate(12.5e6); + } + else if (mb_name.find("B100") != std::string::npos){ + usrp->set_tx_rate(4e6); + usrp->set_rx_rate(4e6); + } + else if (mb_name.find("E100") != std::string::npos or mb_name.find("E110") != std::string::npos){ + usrp->set_tx_rate(4e6); + usrp->set_rx_rate(8e6); + } + else{ + throw std::runtime_error("self-calibration is not supported for this hardware"); + } + + const uhd::fs_path tx_fe_path = "/mboards/0/dboards/A/tx_frontends/0"; + const std::string tx_name = tree->access<std::string>(tx_fe_path / "name").get(); + if (tx_name.find("WBX") != std::string::npos or tx_name.find("SBX") != std::string::npos){ + usrp->set_tx_gain(0); + } + else{ + throw std::runtime_error("self-calibration is not supported for this hardware"); + } + + const uhd::fs_path rx_fe_path = "/mboards/0/dboards/A/tx_frontends/0"; + const std::string rx_name = tree->access<std::string>(rx_fe_path / "name").get(); + if (rx_name.find("WBX") != std::string::npos or rx_name.find("SBX") != std::string::npos){ + usrp->set_rx_gain(25); + } + else{ + throw std::runtime_error("self-calibration is not supported for this hardware"); + } + +} + +/*********************************************************************** + * Sinusoid wave table + **********************************************************************/ +class wave_table{ +public: + wave_table(const double ampl){ + _table.resize(wave_table_len); + for (size_t i = 0; i < wave_table_len; i++){ + _table[i] = samp_type(std::polar(ampl, (tau*i)/wave_table_len)); + } + } + + inline samp_type operator()(const size_t index) const{ + return _table[index % wave_table_len]; + } + +private: + std::vector<samp_type > _table; +}; + +/*********************************************************************** + * Compute power of a tone + **********************************************************************/ +static inline double compute_tone_dbrms( + const std::vector<samp_type > &samples, + const double freq //freq is fractional +){ + //shift the samples so the tone at freq is down at DC + //and average the samples to measure the DC component + samp_type average = 0; + for (size_t i = 0; i < samples.size(); i++){ + average += samp_type(std::polar(1.0, -freq*tau*i)) * samples[i]; + } + + return 20*std::log10(std::abs(average/float(samples.size()))); +} + +/*********************************************************************** + * Write a dat file + **********************************************************************/ +static inline void write_samples_to_file( + const std::vector<samp_type > &samples, const std::string &file +){ + std::ofstream outfile(file.c_str(), std::ofstream::binary); + outfile.write((const char*)&samples.front(), samples.size()*sizeof(samp_type)); + outfile.close(); +} + +/*********************************************************************** + * Store data to file + **********************************************************************/ +static void store_results( + uhd::usrp::multi_usrp::sptr usrp, + const std::vector<result_t> &results, + const std::string &XX, + const std::string &xx, + const std::string &what +){ + //extract eeprom serial + uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); + const uhd::fs_path db_path = "/mboards/0/dboards/A/" + xx + "_eeprom"; + const uhd::usrp::dboard_eeprom_t db_eeprom = tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get(); + if (db_eeprom.serial.empty()) throw std::runtime_error(XX + " dboard has empty serial!"); + + //make the calibration file path + fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd"; + fs::create_directory(cal_data_path); + cal_data_path = cal_data_path / "cal"; + fs::create_directory(cal_data_path); + cal_data_path = cal_data_path / str(boost::format("%s_%s_cal_v0.1_%s.csv") % xx % what % db_eeprom.serial); + if (fs::exists(cal_data_path)){ + fs::rename(cal_data_path, cal_data_path.string() + str(boost::format(".%d") % time(NULL))); + } + + //fill the calibration file + std::ofstream cal_data(cal_data_path.string().c_str()); + cal_data << boost::format("name, %s Frontend Calibration\n") % XX; + cal_data << boost::format("serial, %s\n") % db_eeprom.serial; + cal_data << boost::format("timestamp, %d\n") % time(NULL); + cal_data << boost::format("version, 0, 1\n"); + cal_data << boost::format("DATA STARTS HERE\n"); + cal_data << "lo_frequency, correction_real, correction_imag, measured, delta\n"; + + for (size_t i = 0; i < results.size(); i++){ + cal_data + << results[i].freq << ", " + << results[i].real_corr << ", " + << results[i].imag_corr << ", " + << results[i].best << ", " + << results[i].delta << "\n" + ; + } + + std::cout << "wrote cal data to " << cal_data_path << std::endl; +} + +/*********************************************************************** + * Data capture routine + **********************************************************************/ +static void capture_samples( + uhd::usrp::multi_usrp::sptr usrp, + uhd::rx_streamer::sptr rx_stream, + std::vector<samp_type > &buff, + const size_t nsamps_requested +){ + buff.resize(nsamps_requested); + uhd::rx_metadata_t md; + + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = buff.size(); + stream_cmd.stream_now = true; + usrp->issue_stream_cmd(stream_cmd); + const size_t num_rx_samps = rx_stream->recv(&buff.front(), buff.size(), md); + + //validate the received data + if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){ + throw std::runtime_error(str(boost::format( + "Unexpected error code 0x%x" + ) % md.error_code)); + } + //we can live if all the data didnt come in + if (num_rx_samps > buff.size()/2){ + buff.resize(num_rx_samps); + return; + } + if (num_rx_samps != buff.size()){ + throw std::runtime_error("did not get all the samples requested"); + } +} diff --git a/host/utils/usrp_n2xx_net_burner.py b/host/utils/usrp_n2xx_net_burner.py new file mode 100755 index 000000000..31c94405b --- /dev/null +++ b/host/utils/usrp_n2xx_net_burner.py @@ -0,0 +1,522 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +# TODO: make it autodetect UHD devices + +import optparse +import math +import os +import re +import struct +import socket +import sys +import time +import platform +import subprocess + +######################################################################## +# constants +######################################################################## +UDP_FW_UPDATE_PORT = 49154 +UDP_MAX_XFER_BYTES = 1024 +UDP_TIMEOUT = 3 +UDP_POLL_INTERVAL = 0.10 #in seconds + +USRP2_FW_PROTO_VERSION = 7 #should be unused after r6 + +#from bootloader_utils.h + +FPGA_IMAGE_SIZE_BYTES = 1572864 +FW_IMAGE_SIZE_BYTES = 31744 +SAFE_FPGA_IMAGE_LOCATION_ADDR = 0x00000000 +SAFE_FW_IMAGE_LOCATION_ADDR = 0x003F0000 +PROD_FPGA_IMAGE_LOCATION_ADDR = 0x00180000 +PROD_FW_IMAGE_LOCATION_ADDR = 0x00300000 + +FLASH_DATA_PACKET_SIZE = 256 + +#see fw_common.h +FLASH_ARGS_FMT = '!LLLLL256s' +FLASH_INFO_FMT = '!LLLLL256x' +FLASH_IP_FMT = '!LLLL260x' +FLASH_HW_REV_FMT = '!LLLL260x' + +n2xx_revs = { + 0x0a00: ["n200_r3", "n200_r2"], + 0x0a10: ["n200_r4"], + 0x0a01: ["n210_r3", "n210_r2"], + 0x0a11: ["n210_r4"] + } + +class update_id_t: + USRP2_FW_UPDATE_ID_WAT = ord(' ') + USRP2_FW_UPDATE_ID_OHAI_LOL = ord('a') + USRP2_FW_UPDATE_ID_OHAI_OMG = ord('A') + USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL = ord('f') + USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG = ord('F') + USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL = ord('e') + USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG = ord('E') + USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL = ord('d') + USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG = ord('D') + USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG = ord('B') + USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL = ord('w') + USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG = ord('W') + USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL = ord('r') + USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG = ord('R') + USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL = ord('s') + USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG = ord('S') + USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL = ord('v') + USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG = ord('V') + USRP2_FW_UPDATE_ID_KTHXBAI = ord('~') + +_seq = -1 +def seq(): + global _seq + _seq = _seq+1 + return _seq + +######################################################################## +# helper functions +######################################################################## +def unpack_flash_args_fmt(s): + return struct.unpack(FLASH_ARGS_FMT, s) #(proto_ver, pktid, seq, flash_addr, length, data) + +def unpack_flash_info_fmt(s): + return struct.unpack(FLASH_INFO_FMT, s) #(proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes) + +def unpack_flash_ip_fmt(s): + return struct.unpack(FLASH_IP_FMT, s) #(proto_ver, pktid, seq, ip_addr) + +def unpack_flash_hw_rev_fmt(s): + return struct.unpack(FLASH_HW_REV_FMT, s) #proto_ver, pktid, seq, hw_rev + +def pack_flash_args_fmt(proto_ver, pktid, seq, flash_addr, length, data=bytes()): + return struct.pack(FLASH_ARGS_FMT, proto_ver, pktid, seq, flash_addr, length, data) + +def pack_flash_info_fmt(proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes): + return struct.pack(FLASH_INFO_FMT, proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes) + +def pack_flash_hw_rev_fmt(proto_ver, pktid, seq, hw_rev): + return struct.pack(FLASH_HW_REV_FMT, proto_ver, pktid, seq, hw_rev) + +def is_valid_fpga_image(fpga_image): + for i in range(0,63): + if fpga_image[i:i+1] == bytes(b'\xFF'): continue + if fpga_image[i:i+2] == bytes(b'\xAA\x99'): return True + return False + +def is_valid_fw_image(fw_image): + return fw_image[:4] == bytes(b'\x0B\x0B\x0B\x0B') + + +######################################################################## +# interface discovery and device enumeration +######################################################################## +def command(*args): + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + ret = p.wait() + verbose = p.stdout.read().decode() + if ret != 0: raise Exception(verbose) + return verbose + +def get_interfaces(): + if(platform.system() is "Windows"): return win_get_interfaces() + else: return unix_get_interfaces() + +def unix_get_interfaces(): + ifconfig = command("/sbin/ifconfig") + ip_addr_re = "cast\D*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" + bcasts = re.findall(ip_addr_re, ifconfig) + return bcasts + +def win_get_interfaces(): + from ctypes import Structure, windll, sizeof + from ctypes import POINTER, byref + from ctypes import c_ulong, c_uint, c_ubyte, c_char + MAX_ADAPTER_DESCRIPTION_LENGTH = 128 + MAX_ADAPTER_NAME_LENGTH = 256 + MAX_ADAPTER_ADDRESS_LENGTH = 8 + class IP_ADDR_STRING(Structure): + pass + LP_IP_ADDR_STRING = POINTER(IP_ADDR_STRING) + IP_ADDR_STRING._fields_ = [ + ("next", LP_IP_ADDR_STRING), + ("ipAddress", c_char * 16), + ("ipMask", c_char * 16), + ("context", c_ulong)] + class IP_ADAPTER_INFO (Structure): + pass + LP_IP_ADAPTER_INFO = POINTER(IP_ADAPTER_INFO) + IP_ADAPTER_INFO._fields_ = [ + ("next", LP_IP_ADAPTER_INFO), + ("comboIndex", c_ulong), + ("adapterName", c_char * (MAX_ADAPTER_NAME_LENGTH + 4)), + ("description", c_char * (MAX_ADAPTER_DESCRIPTION_LENGTH + 4)), + ("addressLength", c_uint), + ("address", c_ubyte * MAX_ADAPTER_ADDRESS_LENGTH), + ("index", c_ulong), + ("type", c_uint), + ("dhcpEnabled", c_uint), + ("currentIpAddress", LP_IP_ADDR_STRING), + ("ipAddressList", IP_ADDR_STRING), + ("gatewayList", IP_ADDR_STRING), + ("dhcpServer", IP_ADDR_STRING), + ("haveWins", c_uint), + ("primaryWinsServer", IP_ADDR_STRING), + ("secondaryWinsServer", IP_ADDR_STRING), + ("leaseObtained", c_ulong), + ("leaseExpires", c_ulong)] + GetAdaptersInfo = windll.iphlpapi.GetAdaptersInfo + GetAdaptersInfo.restype = c_ulong + GetAdaptersInfo.argtypes = [LP_IP_ADAPTER_INFO, POINTER(c_ulong)] + adapterList = (IP_ADAPTER_INFO * 10)() + buflen = c_ulong(sizeof(adapterList)) + rc = GetAdaptersInfo(byref(adapterList[0]), byref(buflen)) + if rc == 0: + for a in adapterList: + adNode = a.ipAddressList + while True: + #convert ipAddr and ipMask into hex addrs that can be turned into a bcast addr + ipAddr = adNode.ipAddress.decode() + ipMask = adNode.ipMask.decode() + if ipAddr and ipMask: + hexAddr = struct.unpack("<L", socket.inet_aton(ipAddr))[0] + hexMask = struct.unpack("<L", socket.inet_aton(ipMask))[0] + if(hexAddr and hexMask): #don't broadcast on 255.255.255.255, that's just lame + yield socket.inet_ntoa(struct.pack("<L", (hexAddr & hexMask) | (~hexMask) & 0xFFFFFFFF)) + adNode = adNode.next + if not adNode: + break + +def enumerate_devices(): + for bcast_addr in get_interfaces(): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + sock.settimeout(0.1) + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_OHAI_LOL, 0, 0, 0) + sock.sendto(out_pkt, (bcast_addr, UDP_FW_UPDATE_PORT)) + still_goin = True + while(still_goin): + try: + pkt = sock.recv(UDP_MAX_XFER_BYTES) + (proto_ver, pktid, rxseq, ip_addr) = unpack_flash_ip_fmt(pkt) + if(pktid == update_id_t.USRP2_FW_UPDATE_ID_OHAI_OMG): + yield socket.inet_ntoa(struct.pack("<L", socket.ntohl(ip_addr))) + except socket.timeout: + still_goin = False + +######################################################################## +# Burner class, holds a socket and send/recv routines +######################################################################## +class burner_socket(object): + def __init__(self, addr): + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.settimeout(UDP_TIMEOUT) + self._sock.connect((addr, UDP_FW_UPDATE_PORT)) + self.set_callbacks(lambda *a: None, lambda *a: None) + self.init_update() #check that the device is there + self.get_hw_rev() + + def set_callbacks(self, progress_cb, status_cb): + self._progress_cb = progress_cb + self._status_cb = status_cb + + def send_and_recv(self, pkt): + self._sock.send(pkt) + return self._sock.recv(UDP_MAX_XFER_BYTES) + + #just here to validate comms + def init_update(self): + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_OHAI_LOL, seq(), 0, 0) + try: in_pkt = self.send_and_recv(out_pkt) + except socket.timeout: raise Exception("No response from device") + (proto_ver, pktid, rxseq, ip_addr) = unpack_flash_ip_fmt(in_pkt) + if pktid == update_id_t.USRP2_FW_UPDATE_ID_OHAI_OMG: + print("USRP-N2XX found.") + else: + raise Exception("Invalid reply received from device.") + + def get_hw_rev(self): + out_pkt = pack_flash_hw_rev_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL, seq(), 0) + in_pkt = self.send_and_recv(out_pkt) + (proto_ver, pktid, rxseq, hw_rev) = unpack_flash_hw_rev_fmt(in_pkt) + if(pktid != update_id_t.USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG): hw_rev = 0 + return socket.ntohs(hw_rev) + + memory_size_bytes = 0 + sector_size_bytes = 0 + def get_flash_info(self): + if (self.memory_size_bytes != 0) and (self.sector_size_bytes != 0): + return (self.memory_size_bytes, self.sector_size_bytes) + + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL, seq(), 0, 0) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, self.sector_size_bytes, self.memory_size_bytes) = unpack_flash_info_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + return (self.memory_size_bytes, self.sector_size_bytes) + + def burn_fw(self, fw, fpga, reset, safe, check_rev=True): + (flash_size, sector_size) = self.get_flash_info() + hw_rev = self.get_hw_rev() + + if n2xx_revs.has_key(hw_rev): print("Hardware type: %s" % n2xx_revs[hw_rev][0]) + print("Flash size: %i\nSector size: %i\n" % (flash_size, sector_size)) + + if fpga: + #validate fpga image name against hardware rev + if(check_rev and hw_rev != 0 and not any(name in fpga for name in n2xx_revs[hw_rev])): + raise Exception("Error: incorrect FPGA image version. Please use the correct image for device %s" % n2xx_revs[hw_rev][0]) + + if safe: image_location = SAFE_FPGA_IMAGE_LOCATION_ADDR + else: image_location = PROD_FPGA_IMAGE_LOCATION_ADDR + + fpga_file = open(fpga, 'rb') + fpga_image = fpga_file.read() + + if len(fpga_image) > FPGA_IMAGE_SIZE_BYTES: + raise Exception("Error: FPGA image file too large.") + + if not is_valid_fpga_image(fpga_image): + raise Exception("Error: Invalid FPGA image file.") + + if (len(fpga_image) + image_location) > flash_size: + raise Exception("Error: Cannot write past end of device") + + print("Begin FPGA write: this should take about 1 minute...") + start_time = time.time() + self.erase_image(image_location, FPGA_IMAGE_SIZE_BYTES) + self.write_image(fpga_image, image_location) + self.verify_image(fpga_image, image_location) + print("Time elapsed: %f seconds"%(time.time() - start_time)) + print("\n\n") + + if fw: + if safe: image_location = SAFE_FW_IMAGE_LOCATION_ADDR + else: image_location = PROD_FW_IMAGE_LOCATION_ADDR + + fw_file = open(fw, 'rb') + fw_image = fw_file.read() + + if len(fw_image) > FW_IMAGE_SIZE_BYTES: + raise Exception("Error: Firmware image file too large.") + + if not is_valid_fw_image(fw_image): + raise Exception("Error: Invalid firmware image file.") + + if (len(fw_image) + image_location) > flash_size: + raise Exception("Error: Cannot write past end of device") + + print("Begin firmware write: this should take about 1 second...") + start_time = time.time() + self.erase_image(image_location, FW_IMAGE_SIZE_BYTES) + self.write_image(fw_image, image_location) + self.verify_image(fw_image, image_location) + print("Time elapsed: %f seconds"%(time.time() - start_time)) + print("\n\n") + + if reset: self.reset_usrp() + + def write_image(self, image, addr): + print("Writing image") + self._status_cb("Writing") + writedata = image + #we split the image into smaller (256B) bits and send them down the wire + (mem_size, sector_size) = self.get_flash_info() + if (addr + len(writedata)) > mem_size: + raise Exception("Error: Cannot write past end of device") + + while writedata: + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL, seq(), addr, FLASH_DATA_PACKET_SIZE, writedata[:FLASH_DATA_PACKET_SIZE]) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + writedata = writedata[FLASH_DATA_PACKET_SIZE:] + addr += FLASH_DATA_PACKET_SIZE + self._progress_cb(float(len(image)-len(writedata))/len(image)) + + def verify_image(self, image, addr): + print("Verifying data") + self._status_cb("Verifying") + readsize = len(image) + readdata = bytes() + while readsize > 0: + if readsize < FLASH_DATA_PACKET_SIZE: thisreadsize = readsize + else: thisreadsize = FLASH_DATA_PACKET_SIZE + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL, seq(), addr, thisreadsize) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + readdata += data[:thisreadsize] + readsize -= FLASH_DATA_PACKET_SIZE + addr += FLASH_DATA_PACKET_SIZE + self._progress_cb(float(len(readdata))/len(image)) + + print("Read back %i bytes" % len(readdata)) + # print readdata + + # for i in range(256, 512): + # print "out: %i in: %i" % (ord(image[i]), ord(readdata[i])) + + if readdata != image: + raise Exception("Verify failed. Image did not write correctly.") + else: + print("Success.") + + def read_image(self, image, size, addr): + print("Reading image") + readsize = size + readdata = str() + while readsize > 0: + if readsize < FLASH_DATA_PACKET_SIZE: thisreadsize = readsize + else: thisreadsize = FLASH_DATA_PACKET_SIZE + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL, seq(), addr, thisreadsize) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + readdata += data[:thisreadsize] + readsize -= FLASH_DATA_PACKET_SIZE + addr += FLASH_DATA_PACKET_SIZE + + print("Read back %i bytes" % len(readdata)) + + #write to disk + f = open(image, 'w') + f.write(readdata) + f.close() + + def reset_usrp(self): + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL, seq(), 0, 0) + try: in_pkt = self.send_and_recv(out_pkt) + except socket.timeout: return + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + if pktid == update_id_t.USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG: + raise Exception("Device failed to reset.") + + def erase_image(self, addr, length): + self._status_cb("Erasing") + #get flash info first + (flash_size, sector_size) = self.get_flash_info() + if (addr + length) > flash_size: + raise Exception("Cannot erase past end of device") + + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL, seq(), addr, length) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + print("Erasing %i bytes at %i" % (length, addr)) + start_time = time.time() + + #now wait for it to finish + while(True): + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL, seq(), 0, 0) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid == update_id_t.USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG: break + elif pktid != update_id_t.USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + time.sleep(0.01) #decrease network overhead by waiting a bit before polling + self._progress_cb(min(1.0, (time.time() - start_time)/(length/80e3))) + + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = optparse.OptionParser() + parser.add_option("--addr", type="string", help="USRP-N2XX device address", default='') + parser.add_option("--fw", type="string", help="firmware image path (optional)", default='') + parser.add_option("--fpga", type="string", help="fpga image path (optional)", default='') + parser.add_option("--reset", action="store_true", help="reset the device after writing", default=False) + parser.add_option("--read", action="store_true", help="read to file instead of write from file", default=False) + parser.add_option("--overwrite-safe", action="store_true", help="never ever use this option", default=False) + parser.add_option("--dont-check-rev", action="store_true", help="disable revision checks", default=False) + parser.add_option("--list", action="store_true", help="list possible network devices", default=False) + (options, args) = parser.parse_args() + + return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + + if options.list: + print('Possible network devices:') + print(' ' + '\n '.join(enumerate_devices())) + exit() + + if not options.addr: raise Exception('no address specified') + + if not options.fpga and not options.fw and not options.reset: raise Exception('Must specify either a firmware image or FPGA image, and/or reset.') + + if options.overwrite_safe and not options.read: + print("Are you REALLY, REALLY sure you want to overwrite the safe image? This is ALMOST ALWAYS a terrible idea.") + print("If your image is faulty, your USRP2+ will become a brick until reprogrammed via JTAG.") + response = raw_input("""Type "yes" to continue, or anything else to quit: """) + if response != "yes": sys.exit(0) + + burner = burner_socket(addr=options.addr) + + if options.read: + if options.fw: + file = options.fw + if os.path.isfile(file): + response = raw_input("File already exists -- overwrite? (y/n) ") + if response != "y": sys.exit(0) + size = FW_IMAGE_SIZE_BYTES + addr = SAFE_FW_IMAGE_LOCATION_ADDR if options.overwrite_safe else PROD_FW_IMAGE_LOCATION_ADDR + burner.read_image(file, size, addr) + + if options.fpga: + file = options.fpga + if os.path.isfile(file): + response = input("File already exists -- overwrite? (y/n) ") + if response != "y": sys.exit(0) + size = FPGA_IMAGE_SIZE_BYTES + addr = SAFE_FPGA_IMAGE_LOCATION_ADDR if options.overwrite_safe else PROD_FPGA_IMAGE_LOCATION_ADDR + burner.read_image(file, size, addr) + + else: burner.burn_fw(fw=options.fw, fpga=options.fpga, reset=options.reset, safe=options.overwrite_safe, check_rev=not options.dont_check_rev) diff --git a/host/utils/usrp_n2xx_net_burner_gui.py b/host/utils/usrp_n2xx_net_burner_gui.py new file mode 100755 index 000000000..e2b79e72c --- /dev/null +++ b/host/utils/usrp_n2xx_net_burner_gui.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import usrp_n2xx_net_burner #import implementation +try: + import tkinter, tkinter.filedialog, tkinter.font, tkinter.messagebox +except ImportError: + import tkFileDialog, tkFont, tkMessageBox + import Tkinter as tkinter + tkinter.filedialog = tkFileDialog + tkinter.font = tkFont + tkinter.messagebox = tkMessageBox +import os + +class BinFileEntry(tkinter.Frame): + """ + Simple file entry widget for getting the file path of bin files. + Combines a label, entry, and button with file dialog callback. + """ + + def __init__(self, root, what, def_path=''): + self._what = what + tkinter.Frame.__init__(self, root) + tkinter.Label(self, text=what+":").pack(side=tkinter.LEFT) + self._entry = tkinter.Entry(self, width=50) + self._entry.insert(tkinter.END, def_path) + self._entry.pack(side=tkinter.LEFT) + tkinter.Button(self, text="...", command=self._button_cb).pack(side=tkinter.LEFT) + + def _button_cb(self): + filename = tkinter.filedialog.askopenfilename( + parent=self, + filetypes=[('bin files', '*.bin'), ('all files', '*.*')], + title="Select bin file for %s"%self._what, + initialdir=os.path.dirname(self.get_filename()), + ) + + # open file on your own + if filename: + self._entry.delete(0, tkinter.END) + self._entry.insert(0, filename) + + def get_filename(self): + return self._entry.get() + +class ProgressBar(tkinter.Canvas): + """ + A simple implementation of a progress bar. + Draws rectangle that fills from left to right. + """ + + def __init__(self, root, width=500, height=20): + self._width = width + self._height = height + tkinter.Canvas.__init__(self, root, relief="sunken", borderwidth=2, width=self._width-2, height=self._height-2) + self._last_fill_pixels = None + self.set(0.0) + + def set(self, frac): + """ + Update the progress where fraction is between 0.0 and 1.0 + """ + #determine the number of pixels to draw + fill_pixels = int(round(self._width*frac)) + if fill_pixels == self._last_fill_pixels: return + self._last_fill_pixels = fill_pixels + + #draw a rectangle representing the progress + if frac: self.create_rectangle(0, 0, fill_pixels, self._height, fill="#357EC7") + else: self.create_rectangle(0, 0, self._width, self._height, fill="#E8E8E8") + +class DeviceEntryWidget(tkinter.Frame): + """ + Simple entry widget for getting the network device name. + Combines a label, entry, and helpful text box with hints. + """ + + def __init__(self, root, text=''): + tkinter.Frame.__init__(self, root) + + tkinter.Button(self, text="Rescan for Devices", command=self._reload_cb).pack() + + self._hints = tkinter.Listbox(self) + self._hints.bind("<<ListboxSelect>>", self._listbox_cb) + self._reload_cb() + self._hints.pack(expand=tkinter.YES, fill=tkinter.X) + + frame = tkinter.Frame(self) + frame.pack() + + tkinter.Label(frame, text="Network Address:").pack(side=tkinter.LEFT) + self._entry = tkinter.Entry(frame, width=50) + self._entry.insert(tkinter.END, text) + self._entry.pack(side=tkinter.LEFT) + + def _reload_cb(self): + self._hints.delete(0, tkinter.END) + for hint in usrp_n2xx_net_burner.enumerate_devices(): + self._hints.insert(tkinter.END, hint) + + def _listbox_cb(self, event): + try: + sel = self._hints.get(self._hints.curselection()[0]) + self._entry.delete(0, tkinter.END) + self._entry.insert(0, sel) + except Exception as e: print(e) + + def get_devname(self): + return self._entry.get() + +class SectionLabel(tkinter.Label): + """ + Make a text label with bold font. + """ + + def __init__(self, root, text): + tkinter.Label.__init__(self, root, text=text) + + #set the font bold + f = tkinter.font.Font(font=self['font']) + f['weight'] = 'bold' + self['font'] = f.name + +class USRPN2XXNetBurnerApp(tkinter.Frame): + """ + The top level gui application for the usrp-n2xx network burner. + Creates entry widgets and button with callback to write images. + """ + + def __init__(self, root, addr, fw, fpga): + + tkinter.Frame.__init__(self, root) + + #pack the file entry widgets + SectionLabel(self, text="Select Images").pack(pady=5) + self._fw_img_entry = BinFileEntry(self, "Firmware Image", def_path=fw) + self._fw_img_entry.pack() + self._fpga_img_entry = BinFileEntry(self, "FPGA Image", def_path=fpga) + self._fpga_img_entry.pack() + + #pack the destination entry widget + SectionLabel(self, text="Select Device").pack(pady=5) + self._net_dev_entry = DeviceEntryWidget(self, text=addr) + self._net_dev_entry.pack() + + #the do it button + SectionLabel(self, text="").pack(pady=5) + button = tkinter.Button(self, text="Burn Images", command=self._burn) + self._enable_input = lambda: button.configure(state=tkinter.NORMAL) + self._disable_input = lambda: button.configure(state=tkinter.DISABLED) + button.pack() + + #a progress bar to monitor the status + progress_frame = tkinter.Frame(self) + progress_frame.pack() + self._status = tkinter.StringVar() + tkinter.Label(progress_frame, textvariable=self._status).pack(side=tkinter.LEFT) + self._pbar = ProgressBar(progress_frame) + self._pbar.pack(side=tkinter.RIGHT, expand=True) + + def _burn(self): + #grab strings from the gui + fw = self._fw_img_entry.get_filename() + fpga = self._fpga_img_entry.get_filename() + addr = self._net_dev_entry.get_devname() + + #check input + if not addr: + tkinter.messagebox.showerror('Error:', 'No address specified!') + return + if not fw and not fpga: + tkinter.messagebox.showerror('Error:', 'No images specified!') + return + if fw and not os.path.exists(fw): + tkinter.messagebox.showerror('Error:', 'Firmware image not found!') + return + if fpga and not os.path.exists(fpga): + tkinter.messagebox.showerror('Error:', 'FPGA image not found!') + return + + self._disable_input() + try: + #make a new burner object and attempt the burner operation + burner = usrp_n2xx_net_burner.burner_socket(addr=addr) + + for (image_type, fw_img, fpga_img) in (('FPGA', '', fpga), ('Firmware', fw, '')): + #setup callbacks that update the gui + def status_cb(status): + self._pbar.set(0.0) #status change, reset the progress + self._status.set("%s %s "%(status.title(), image_type)) + self.update() + def progress_cb(progress): + self._pbar.set(progress) + self.update() + burner.set_callbacks(progress_cb=progress_cb, status_cb=status_cb) + burner.burn_fw(fw=fw_img, fpga=fpga_img, reset=False, safe=False) + + if tkinter.messagebox.askyesno("Burn was successful!", "Reset the device?"): + burner.reset_usrp() + + except Exception as e: + tkinter.messagebox.showerror('Verbose:', 'Error: %s'%str(e)) + + #reset the progress bar + self._pbar.set(0.0) + self._status.set("") + self._enable_input() + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = usrp_n2xx_net_burner.get_options() + root = tkinter.Tk() + root.title('USRP-N2XX Net Burner') + USRPN2XXNetBurnerApp(root, addr=options.addr, fw=options.fw, fpga=options.fpga).pack() + root.mainloop() + exit() |