diff --git a/CMakeLists.txt b/CMakeLists.txt index af11d11..84c2bd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # -# (C) Copyright 2011-2024 Johns Hopkins University (JHU), All Rights Reserved. +# (C) Copyright 2011-2025 Johns Hopkins University (JHU), All Rights Reserved. # # --- begin cisst license - do not edit --- # @@ -9,7 +9,7 @@ # # --- end cisst license --- -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.16) project (Amp1394 VERSION 2.2.0) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b5fd88c..f6ad980 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,5 +1,5 @@ # -# (C) Copyright 2011-2021 Johns Hopkins University (JHU), All Rights Reserved. +# (C) Copyright 2011-2025 Johns Hopkins University (JHU), All Rights Reserved. # # --- begin cisst license - do not edit --- # @@ -65,29 +65,31 @@ if (Amp1394_BUILD_SWIG) find_package(SWIG REQUIRED) include(${SWIG_USE_FILE}) - find_package(PythonLibs) - include_directories(${PYTHON_INCLUDE_PATH}) + # Find Python and NumPy + set (AMP1394_PYTHON_VERSION_REQUIRED "" CACHE STRING "Required Python version (if not empty)") + if (AMP1394_PYTHON_VERSION_REQUIRED) + message (STATUS "Looking for Python ${AMP1394_PYTHON_VERSION_REQUIRED}") + find_package (Python ${AMP1394_PYTHON_VERSION_REQUIRED} EXACT REQUIRED COMPONENTS Development NumPy) + else () + find_package (Python REQUIRED COMPONENTS Interpreter Development NumPy) + endif () - find_package(PythonInterp) - if (PYTHON_EXECUTABLE) - execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import numpy; print(numpy.get_include())" - ERROR_QUIET - OUTPUT_VARIABLE Amp1394_NUMPY_INCLUDE_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - include_directories(${Amp1394_NUMPY_INCLUDE_DIR} "${Amp1394_BINARY_DIR}/Amp1394") - endif() + include_directories(${Python_INCLUDE_DIRS} ${Python_NumPy_INCLUDE_DIRS}) + include_directories("${Amp1394_BINARY_DIR}/Amp1394") set_source_files_properties(Amp1394.i PROPERTIES CPLUSPLUS ON) - swig_add_module(Amp1394Python python Amp1394.i ${HEADERS} ${SOURCE_FILES}) - swig_link_libraries(Amp1394Python ${PYTHON_LIBRARIES} ${Amp1394_EXTRA_LIBRARIES}) + swig_add_library(Amp1394Python + LANGUAGE python + SOURCES Amp1394.i ${HEADERS} ${SOURCE_FILES}) + target_link_libraries(Amp1394Python ${Python_LIBRARIES} ${Amp1394_EXTRA_LIBRARIES}) if (WIN32) - set_target_properties (_Amp1394Python PROPERTIES SUFFIX .pyd) - set_target_properties (_Amp1394Python PROPERTIES DEBUG_POSTFIX "_d") + set_target_properties (Amp1394Python PROPERTIES SUFFIX .pyd) + set_target_properties (Amp1394Python PROPERTIES DEBUG_POSTFIX "_d") endif (WIN32) # post build command - add_custom_command(TARGET _Amp1394Python POST_BUILD + add_custom_command(TARGET Amp1394Python POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/Amp1394Python.py @@ -104,7 +106,7 @@ if (Amp1394_BUILD_SWIG) # install library and python file install ( - TARGETS _Amp1394Python + TARGETS Amp1394Python RUNTIME DESTINATION bin LIBRARY DESTINATION lib COMPONENT Amp1394) diff --git a/lib/FpgaIO.h b/lib/FpgaIO.h index 722c1ba..a5b5884 100644 --- a/lib/FpgaIO.h +++ b/lib/FpgaIO.h @@ -361,6 +361,9 @@ class FpgaIO : public BoardIO // data Data to write to register bool WriteFirewirePhy(unsigned char addr, unsigned char data); + // Print FireWire debug data + static void PrintFirewireDebug(std::ostream &debugStream, const quadlet_t *data); + protected: // Accumulated firmware time diff --git a/lib/ZynqEmioPort.h b/lib/ZynqEmioPort.h index 29f0388..681e72a 100644 --- a/lib/ZynqEmioPort.h +++ b/lib/ZynqEmioPort.h @@ -74,6 +74,14 @@ class ZynqEmioPort : public BasePort { ZynqEmioPort(int portNum = 0, std::ostream &debugStream = std::cerr); ~ZynqEmioPort(); + // Get/set EMIO timeout in microseconds + double GetTimeout_us(void) const { return emio->GetTimeout_us(); } + void SetTimeout_us(double time_uSec) { emio->SetTimeout_us(time_uSec); } + + // Get/set EMIO verbose flag + bool GetVerbose() const { return emio->GetVerbose(); } + void SetVerbose(bool newState) { emio->SetVerbose(newState); } + //****************** BasePort pure virtual methods *********************** PortType GetPortType(void) const { return PORT_ZYNQ_EMIO; } diff --git a/lib/code/Amp1394Console.cpp b/lib/code/Amp1394Console.cpp index cc3cd8d..20cab70 100644 --- a/lib/code/Amp1394Console.cpp +++ b/lib/code/Amp1394Console.cpp @@ -4,7 +4,7 @@ /* Author(s): Peter Kazanzides - (C) Copyright 2021 Johns Hopkins University (JHU), All Rights Reserved. + (C) Copyright 2021-2025 Johns Hopkins University (JHU), All Rights Reserved. --- begin cisst license - do not edit --- @@ -29,6 +29,7 @@ no warranty. The complete license can be found in license.txt and #include #else #include +#include #endif #endif @@ -87,6 +88,19 @@ bool Amp1394Console::GetString(char *str, int n) struct Amp1394Console::ConsoleInternals { struct termios savedAttr; }; + +// Implementation of _kbhit for non-Windows systems +int _kbhit() +{ + struct timeval tv; + fd_set fds; + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + select(STDIN_FILENO+1, &fds, 0, 0, &tv); + return FD_ISSET(STDIN_FILENO, &fds); +} #endif bool Amp1394Console::Init() @@ -169,14 +183,15 @@ void Amp1394Console::Refresh() int Amp1394Console::GetChar() { -#ifdef _MSC_VER int c = -1; // ERR in curses - // If blocking, or if key pressed, call _getch - if (!noBlock || _kbhit()) + // If blocking, or if key pressed, get a character + if (!noBlock || _kbhit()) { +#ifdef _MSC_VER c = noEcho ? _getch() : _getche(); #else - int c = getchar(); + c = getchar(); #endif + } return c; } diff --git a/lib/code/AmpIO.cpp b/lib/code/AmpIO.cpp index 567ae0e..b7814d0 100644 --- a/lib/code/AmpIO.cpp +++ b/lib/code/AmpIO.cpp @@ -266,23 +266,27 @@ std::string AmpIO::GetQLASerialNumber(unsigned char chan) // Format: QLA 1234-56 or QLA 1234-567. // String is terminated by 0 or 0xff. uint16_t address = 0x0000; - uint8_t data[20]; + // QLASNSize must be a multiple of 4 const size_t QLASNSize = 12; + uint8_t data[QLASNSize+1]; std::string sn; data[QLASNSize] = 0; // make sure null-terminated - for (size_t i = 0; i < QLASNSize; i++) { - if (!PromReadByte25AA128(address, data[i], chan)) { - if (chan == 0) - std::cerr << "AmpIO::GetQLASerialNumber: failed to get QLA Serial Number" << std::endl; - else - std::cerr << "AmpIO::GetQLASerialNumber: failed to get QLA " << static_cast(chan) - << " Serial Number" << std::endl; - break; + if (!PromReadBlock25AA128(address, reinterpret_cast(data), QLASNSize/sizeof(quadlet_t), chan)) { + if (chan == 0) + std::cerr << "AmpIO::GetQLASerialNumber: failed to get QLA Serial Number" << std::endl; + else + std::cerr << "AmpIO::GetQLASerialNumber: failed to get QLA " << static_cast(chan) + << " Serial Number" << std::endl; + } + else { + // Convert any trailing 0xff characters to null (0) + for (int i = QLASNSize-1; i >= 0; i--) { + if (data[i] == 0xff) + data[i] = 0; + else if (data[i] != 0) + break; } - if (data[i] == 0xff) - data[i] = 0; - address += 1; } if (strncmp((char *)data, "QLA ", 4) == 0 || strncmp((char *)data, "dRA ", 4) == 0) sn.assign((char *)data+4); @@ -1862,10 +1866,10 @@ void AmpIO::CheckCollectCallback() //******* Following methods are for dRA1 ******** -std::string AmpIO::ExplainSiFault() const +std::string AmpIO::ExplainSiFault(void) const { if (GetHardwareVersion() != dRA1_String) { - return "Not a Si controller"; + return "Sorry, no detailed error code available for QLA based controllers."; } std::stringstream ss; const char* amp_fault_text[16] = {"-", "ADC saturated", "Current deviation", "HW overcurrent", "HW overtemp", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined"}; @@ -1876,7 +1880,7 @@ std::string AmpIO::ExplainSiFault() const if (!(status & (1 << 0))) ss << "ESPM->dVRK communication failed. Robot not programmed? Cables?" << std::endl; if ((status & (1 << 0)) && !(status & (1 << 1))) ss << "ESII/CC->ESPM communication failed. The problem is inside the robot." << std::endl; if (!(status & (1 << 3))) ss << "Encoder preload is out of sync. You must preload encoder at least once." << std::endl; - if (!GetPowerStatus()) ss << "48V bad. E-stop pressed?" << std::endl; + if (!GetPowerStatus()) ss << "48V bad. E-stop disconnected or button pressed?" << std::endl; if (GetWatchdogTimeoutStatus()) ss << "Watchdog timeout." << std::endl; ss << "(end)" << std::endl; ss << std::endl; diff --git a/lib/code/FpgaIO.cpp b/lib/code/FpgaIO.cpp index 201b35f..46f6791 100644 --- a/lib/code/FpgaIO.cpp +++ b/lib/code/FpgaIO.cpp @@ -435,6 +435,10 @@ bool FpgaIO::PromReadBlock25AA128(uint16_t addr, quadlet_t *data, unsigned int n if (!port->WriteQuadlet(BoardId, address, write_data)) return false; + // TODO: rather than PromDelay, can implement WaitProm25AA128, which can check + // the lowest 3 bits of the status register (0 -> IDLE) + port->PromDelay(); + // get result if (!port->ReadBlock(BoardId, (address|0x0100), data, nquads * 4)) return false; @@ -760,3 +764,59 @@ bool FpgaIO::WriteFirewirePhy(unsigned char addr, unsigned char data) quadlet_t write_data = 0x00001000 | static_cast(addr) << 8 | static_cast(data); return port->WriteQuadlet(BoardId, BoardIO::FW_PHY_REQ, write_data); } + + +void FpgaIO::PrintFirewireDebug(std::ostream &debugStream, const quadlet_t *data) +{ + // Following structure must match DebugData in Firewire.v + struct DebugData { + char header[4]; // Quad 0 + uint8_t misc_info; // Quad 1 + uint8_t bus_info; + uint8_t lreq_info; + uint8_t state; + uint8_t numPhyStatus; // Quad 2 + uint8_t numTxGrant; + uint8_t numRecv; + uint8_t numHubSendReq; + uint16_t stcount; // Quad 3 + uint8_t numPktRecv; + uint8_t numEthSendReq; + }; + const DebugData *p = reinterpret_cast(data); + if (strncmp(p->header, "DBG", 3) != 0) { + debugStream << " PrintFirewireDebug: Unexpected header string: " << p->header[0] + << p->header[1] << p->header[2] << " (should be DBG)" << std::endl; + return; + } + debugStream << " State: " << static_cast(p->state >> 4) + << ", Next: " << static_cast(p->state & 0xf0) << std::endl; + if (p->lreq_info & 0x80) + debugStream << " lreq_type: " << static_cast((p->lreq_info & 0x70) >> 4) << std::endl; + if (p->lreq_info & 0x08) + debugStream << " lreq_busy" << std::endl; + debugStream << " tx_type: " << static_cast(p->lreq_info & 0x07) << std::endl; + debugStream << " "; + if (p->bus_info & 0x80) + debugStream << "req_write_bus "; + if (p->bus_info & 0x40) + debugStream << "grant_write_bus "; + debugStream << "node_id: " << static_cast(p->bus_info & 0x3f) << std::endl; + debugStream << " "; + if (p->misc_info & 0x80) + debugStream << "data_block "; + if (p->misc_info & 0x08) + debugStream << "recvCtlInvalid "; + if (p->misc_info & 0x04) + debugStream << "recvPktNull "; + if (p->misc_info & 0x02) + debugStream << "link_active "; + debugStream << " rx_speed: " << static_cast((p->misc_info & 0x70)>>4) << std::endl; + debugStream << " numHubSendReq: " << static_cast(p->numHubSendReq) << std::endl; + debugStream << " numRecv: " << static_cast(p->numRecv) << std::endl; + debugStream << " numPktRecv: " << static_cast(p->numPktRecv) << std::endl; + debugStream << " numTxGrant: " << static_cast(p->numTxGrant) << std::endl; + debugStream << " numPhyStatus: " << static_cast(p->numPhyStatus) << std::endl; + debugStream << " numEthSendReq: " << static_cast(p->numEthSendReq) << std::endl; + debugStream << " stcount: " << p->stcount << std::endl; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1842470..8de2df5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,8 +41,8 @@ target_link_libraries(qlacloserelays ${Amp1394_LIBRARIES} ${Amp1394_EXTRA_LIBRAR add_executable(qlacommand qlacommand.cpp) target_link_libraries(qlacommand ${Amp1394_LIBRARIES} ${Amp1394_EXTRA_LIBRARIES}) -add_executable(eth1394Test mainEth1394.cpp MotorVoltage.h MotorVoltage.cpp) -target_link_libraries(eth1394Test ${Amp1394_LIBRARIES} ${Amp1394_EXTRA_LIBRARIES}) +add_executable(fpgatest fpgatest.cpp MotorVoltage.h MotorVoltage.cpp) +target_link_libraries(fpgatest ${Amp1394_LIBRARIES} ${Amp1394_EXTRA_LIBRARIES}) add_executable(instrument instrument.cpp) target_link_libraries(instrument ${Amp1394_LIBRARIES} ${Amp1394_EXTRA_LIBRARIES}) @@ -67,7 +67,7 @@ install (PROGRAMS ${EXECUTABLE_OUTPUT_PATH}/quad1394eth COMPONENT Amp1394-utils DESTINATION bin) -install (TARGETS qlacloserelays qlacommand eth1394Test instrument block1394eth enctest ethswitch +install (TARGETS qlacloserelays qlacommand fpgatest instrument block1394eth enctest ethswitch COMPONENT Amp1394-utils RUNTIME DESTINATION bin) diff --git a/tests/dvrktest.cpp b/tests/dvrktest.cpp index ef07227..3e37027 100644 --- a/tests/dvrktest.cpp +++ b/tests/dvrktest.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -148,34 +149,113 @@ const std::string COLOR_OFF = "\033[0m"; const std::string COLOR_ERROR = "\033[0;91m"; const std::string COLOR_GOOD = "\033[0;32m"; + +/**************************************************************** +* @brief: This function logs debug information to console and logfile simultaneously. +* +* @details: A variadic template function that accepts any number of streamable +* arguments, combines them into a single string, and outputs the result to both +* standard output and the logfile with a trailing newline. +* +* @param Args (&&... args): Variable number of arguments of any streamable type +* that can be inserted into an output stream with the +* << operator +* +* @return none +****************************************************************/ +template +void logDebugInfo(Args&&... args) { + std::stringstream ss; + + (void) std::initializer_list { (ss << std::forward(args), 0)...}; + + std::cout << ss.str(); + logfile << ss.str(); +} + +/**************************************************************** +* @brief: This function logs a passing test result to console and logfile. +* +* @details: Outputs a standardized PASS message to standard output and +* logs a corresponding pass message to the logfile. +* @return none +****************************************************************/ +void logPass() { + std::cout << PASS << std::endl; + logfile << "... PASS" << std::endl; +} + +/**************************************************************** +* @brief: This function logs a failing test result to console and logfile. +* +* @details: Outputs a standardized FAIL message to standard output, logs a +* corresponding fail message to the logfile, and sets the result parameter +* to the specified failure type. +* +* @param result (Result &): Reference to the result variable to be updated +* @param resultType (Result): Type of failure (fail or fatal_fail) to assign +* +* @return none +****************************************************************/ +void logFail(Result &result, Result resultType) { + std::cout << FAIL << std::endl; + logfile << "... FAIL" << std::endl; + result = resultType; +} + + +/**************************************************************** +* @brief: Function to test FPGA and QLA serial numbers and test FPGA and board hardware +* versions and firmware version of each board in the dVRK. +* +* @details: This function outputs the serial number and hardware version of the FPGAs +* and the QLAs on each board as well as the firmware version and hardware version of +* each board to standard out and the logfile. +* +* @param AmpIO (**Board): Array of board objects to have this function performed on +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* +* @return testResult (Result): result object indicating pass, fail, or fatal_fail +* status of the test (see note below) +* +* @note: This functions' return does not indicate success or failure for the FPGA, QLA, +* or board. It indicates if there is an unexpected board type or inability to communicate +* with the board. +* @note: This test is for classic dVRK controllers. If tested on a non classic one, +* the function will return a result of fatal failure. Additionally, if unable to read +* from the board, it will also return a fatal failure. +****************************************************************/ Result TestSerialNumberAndVersion(AmpIO **Board, BasePort *Port) { Result result = pass; if (Port->ReadAllBoards()) { for (int board_index = 0; board_index < NUM_FPGA; board_index++) { - std::cout << "Board " << board_index << " - "; - logfile << "Board " << board_index << " - "; + // Read Board Number, FGPA serial number and hardware version + logDebugInfo("Board ", std::to_string(board_index), " - "); std::string FPGA_SN = Board[board_index]->GetFPGASerialNumber(); - std::cout << "FPGA_sn=" << FPGA_SN << " "; - logfile << "FPGA_sn=" << FPGA_SN << " "; + logDebugInfo("FPGA_sn=", FPGA_SN, " "); std::string HW_STRING = Board[board_index]->GetHardwareVersionString(); - std::cout << "HW_ver=" << HW_STRING << " "; - logfile << "HW_ver=" << HW_STRING << " "; + logDebugInfo("HW_ver=", HW_STRING, " "); std::cout << std::endl; + + // Read QLA hardware version if (is_dqla) { std::string QLA1_SN = Board[board_index]->GetQLASerialNumber(1); - std::cout << "QLA1_sn=" << QLA1_SN << " "; - logfile << "QLA1_sn=" << QLA1_SN << " "; + logDebugInfo("QLA1_sn=", QLA1_SN, " "); std::string QLA2_SN = Board[board_index]->GetQLASerialNumber(2); - std::cout << "QLA2_sn=" << QLA2_SN << " "; - logfile << "QLA2_sn=" << QLA2_SN << " "; + logDebugInfo("QLA2_sn=", QLA2_SN, " "); } else { std::string QLA_SN = Board[board_index]->GetQLASerialNumber(); - std::cout << "QLA_sn=" << QLA_SN << " "; - logfile << "QLA_sn=" << QLA_SN << " "; + logDebugInfo("QLA_sn=", QLA_SN, " "); } + + // Read FPGA firmware version uint32_t FPGA_VER = Board[board_index]->GetFirmwareVersion(); - std::cout << "FPGA_ver=" << FPGA_VER << std::endl; - logfile << "FPGA_ver=" << FPGA_VER << std::endl; + logDebugInfo("FPGA_ver=", FPGA_VER, ""); + std::cout << std::endl; + logfile << std::endl; + + // Check for unsupported board type uint32_t hwver = Board[board_index]->GetHardwareVersion(); logfile << "HW_ver=" << Board[board_index]->GetHardwareVersionString() << std::endl; if (hwver == dRA1_String || hwver == BCFG_String) { @@ -192,23 +272,40 @@ Result TestSerialNumberAndVersion(AmpIO **Board, BasePort *Port) { return result; } +/**************************************************************** +* @brief: Function to test analog inputs of the dVRK. +* +* @details: This function tests each channel on each FPGA board to ensure the analog +* inputs (potentiometers that measure the positions of the robotic arms) from the dVRK +* have their expected values (within a specified tolerance) when the motors are adjusted. +* +* @param AmpIO (**Board): Array of board objects to have this function performed on +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* +* @return testResult (Result): result object indicating pass, fail, or fatal_fail +* status of the test +* +* @note: This function outputs debug information to standard out and the logfile; it +* outputs the board, potentiometer channel, the expected and actual measured inputs. +* @note: This test also outputs debugging info if one of the measured potentiometer +* values match the expected value for a different potentiometer. It output that there +* a DB68 SCSI cable is potentially crossed. +****************************************************************/ Result TestAnalogInputs(AmpIO **Board, BasePort *Port) { Result result = pass; Port->ReadAllBoards(); for (int board_index = 0; board_index < NUM_FPGA; board_index++) { for (int channel_index = 0; channel_index < NUM_CHANNEL_PER_FPGA; channel_index++) { + // Get actual potentiometer values unsigned long reading = Board[board_index]->GetAnalogInput(channel_index); - std::cout << std::hex << "board " << board_index << " pot channel " << channel_index << " - " - << " expected=" - << POT_TEST_ADC_COUNT[board_index * 4 + channel_index] << " measured=" << reading; - logfile << std::hex << "board " << board_index << " pot channel " << channel_index << " - " - << " expected=" - << POT_TEST_ADC_COUNT[board_index * 4 + channel_index] << " measured=" << reading; + logDebugInfo(std::hex, "board ", board_index, " pot channel ", channel_index, " - ", " expected=", + POT_TEST_ADC_COUNT[board_index * 4 + channel_index], " measured=", reading); + + // check potentiometer within range of expected value if (std::fabs((long) reading - (long) POT_TEST_ADC_COUNT[board_index * 4 + channel_index]) > POT_TEST_ADC_ERROR_TOLERANCE) { - std::cout << FAIL << std::endl; - logfile << "... FAIL" << std::endl; + logFail(result, fail); logfile << "Tolerance=" << POT_TEST_ADC_ERROR_TOLERANCE << std::endl; - result = fail; // check for swapped cable if (std::fabs((long) reading - (long) POT_TEST_ADC_COUNT[(1 ^ board_index) * 4 + channel_index]) < POT_TEST_ADC_ERROR_TOLERANCE) { @@ -219,8 +316,7 @@ Result TestAnalogInputs(AmpIO **Board, BasePort *Port) { } } else { - std::cout << PASS << std::endl; - logfile << "... PASS" << std::endl; + logPass(); } } } @@ -228,11 +324,36 @@ Result TestAnalogInputs(AmpIO **Board, BasePort *Port) { return result; } +/**************************************************************** +* @brief: Function to test digital inputs of the dVRK. +* +* @details: This function tests the home switches of the dVRK device by checking +* that they switch when they are adjusted. It checks that if it was in its default +* position initially (the home position), it is no longer there, and if it was not +* in default position initially, it now is in its default position. It also checks +* that if an arm is at a limit (i.e. where it's not allowed to move any further in +* some direction), it is no longer at a limit after being moved, and that if it was +* not at a limit, it is at a limit after being moved. +* +* @param AmpIO (**Board): Array of board objects to have this function performed on +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* +* @return testResult (Result): result object indicating pass, fail, or fatal_fail +* status of the test +* +* @note: This function outputs debug information to standard out and the logfile; it +* outputs the digital inputs. +* @note: This test also notes that false positive readings can cause failure and might +* need to run the test again. +****************************************************************/ Result TestDigitalInputs(AmpIO **Board, BasePort *Port) { Result result = pass; std::array before; std::array after; Port->ReadAllBoards(); + + // read all initial switch values before[0] = Board[0]->GetHomeSwitches() & 0xf; if (is_dqla) { before[1] = Board[0]->GetNegativeLimitSwitches() >> 4; @@ -244,6 +365,8 @@ Result TestDigitalInputs(AmpIO **Board, BasePort *Port) { std::this_thread::sleep_for(std::chrono::milliseconds(DIGITAL_IN_TEST_WAIT_TIME_MS)); Port->ReadAllBoards(); + + // read all switch values after waiting after[0] = Board[0]->GetHomeSwitches() & 0xf; if (is_dqla) { after[1] = Board[0]->GetNegativeLimitSwitches() >> 4; @@ -253,22 +376,17 @@ Result TestDigitalInputs(AmpIO **Board, BasePort *Port) { after[2] = Board[1]->GetPositiveLimitSwitches(); } - std::cout << "digital inputs - "; - logfile << "digital inputs - "; - + logDebugInfo("digital inputs - "); for (int i = 0; i < 3; i++) { - std::cout << std::hex << (int) before[i] << "->" << (int) after[i] << " "; - logfile << std::hex << (int) before[i] << "->" << (int) after[i] << " "; + logDebugInfo(std::hex, (int) before[i], "->", (int) after[i], " "); } + // check all switches changed after waiting if ((before == DIGITAL_IN_TEST_HIGH_STATE && after == DIGITAL_IN_TEST_LOW_STATE) || (before == DIGITAL_IN_TEST_LOW_STATE && after == DIGITAL_IN_TEST_HIGH_STATE)) { - std::cout << PASS << std::endl; - logfile << "... PASS" << std::endl; + logPass(); } else { - result = fail; - std::cout << FAIL << std::endl; - logfile << "... FAIL" << std::endl; + logFail(result, fail); std::cout << COLOR_ERROR << "(False-positive failure is possible. Consider it pass if retry OK.)" << COLOR_OFF << std::endl; } @@ -277,12 +395,33 @@ Result TestDigitalInputs(AmpIO **Board, BasePort *Port) { } +/**************************************************************** +* @brief: Function to test encoders' measurements on the dVRK. +* +* @details: This function tests if the encoders properly measure the expected change in +* position after a set amount of time has passed (1000 milliseconds). It also outputs +* the results to standard out and the logfile. +* +* @param AmpIO (**Board): Array of board objects to have this function performed on +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* +* @return testResult (Result): result object indicating pass, fail, or fatal_fail +* status of the test +* +* @note: This function outputs debug information to standard out and the logfile; it +* primarily outputs the measured difference in position and the expected difference. +* @note: This test also outputs debugging info if specific patterns of encoders fail, +* such as encoders 0 through 3 working but 4 through 6 are not, which might indicate +* an issue with a cable. +****************************************************************/ Result TestEncoders(AmpIO **Board, BasePort *Port) { Result result = pass; int64_t encoder_count_before[8]; int64_t encoder_count_after[8]; + // read initial encoder values if (Port->ReadAllBoards()) { for (int board_index = 0; board_index < NUM_FPGA; board_index++) { for (int channel_index = 0; channel_index < NUM_CHANNEL_PER_FPGA; channel_index++) { @@ -293,6 +432,7 @@ Result TestEncoders(AmpIO **Board, BasePort *Port) { std::this_thread::sleep_for(std::chrono::milliseconds(ENCODER_TEST_WAIT_TIME_MS)); + // read encoder values after waiting if (Port->ReadAllBoards()) { for (int board_index = 0; board_index < NUM_FPGA; board_index++) { for (int channel_index = 0; channel_index < NUM_CHANNEL_PER_FPGA; channel_index++) { @@ -305,22 +445,20 @@ Result TestEncoders(AmpIO **Board, BasePort *Port) { uint8_t each_encoder_result = 0; + // check encoder values change is within expected range of expected value for (int i = 0; i < 7; i++) { auto increment = encoder_count_after[i] - encoder_count_before[i]; - std::cout << std::dec << "encoder " << i << " increment - expected=10 measured=" << increment; - logfile << std::dec << "encoder " << i << " increment - expected=10 measured=" << increment; + logDebugInfo(std::dec, "encoder ", i, " increment - expected=10 measured=",increment); if (std::fabs((long) increment - (long) ENCODER_TEST_INCREMENT) < ENCODER_TEST_INCREMENT_TOLERANCE) { - std::cout << PASS << std::endl; - logfile << "... PASS" << std::endl; + logPass(); each_encoder_result |= 1 << i; } else { - std::cout << FAIL << std::endl; - logfile << "... FAIL" << std::endl; - result = fatal_fail; + logFail(result, fatal_fail); } } + // extra debugging information if needed if (each_encoder_result == 0b0001111 || each_encoder_result == 0b1110000 || each_encoder_result == 0) { std::cout << COLOR_ERROR << "(Check SCSI (68-pin) cables!)" << COLOR_OFF << std::endl; @@ -335,70 +473,79 @@ Result TestEncoders(AmpIO **Board, BasePort *Port) { return result; } +/**************************************************************** +* @brief: Function to test power supplies of boards and channels in the dVRK. +* +* @details: This function goes through each board and checks its motor power +* supply/voltage. Then, it proceeds to check each of the channels on the board for +* their amp status (essentially equivalent to the channel's power supply). +* +* @param AmpIO (**Board): Array of board objects to have this function performed on +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* +* @return testResult (Result): result object indicating pass, fail, or fatal_fail +* status of the test +* +* @note: This function outputs debug information to standard out and the logfile. It +* outputs if the issue is the board's power supply or one of the amplifiers. +* @note: This function first checks the relay status on the board to ensure that there +* is a connection for the power supplies to flow. If there is not, the function will fail +* and note that none of the board has power and output debug information to standard +* out accordingly. +****************************************************************/ Result TestMotorPowerControl(AmpIO **Board, BasePort *Port) { Result result = pass; int mv_good_fail_count = 0; + // set initial conditions (enable relays, shutdown watchdog, allow power control) for (int board_index = 0; board_index < NUM_FPGA; board_index++) { Board[board_index]->WriteSafetyRelay(true); Board[board_index]->WriteWatchdogPeriod(0x0000); Board[board_index]->WritePowerEnable(true); Board[board_index]->WriteAmpEnable(0xff, 0xff); } - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + std::this_thread::sleep_for(std::chrono::milliseconds(5000)); Port->ReadAllBoards(); + // check power status and amp status on each board for (int board_index = 0; board_index < NUM_FPGA; board_index++) { auto status = Board[board_index]->GetStatus(); auto safety_relay_status = Board[board_index]->GetSafetyRelayStatus(); - std::cout << std::hex << "board " << board_index << " power - status=" << status << " safety_relay=" - << safety_relay_status; - logfile << std::hex << "board " << board_index << " power - status=" << status << " safety_relay=" - << safety_relay_status; + logDebugInfo(std::hex, "board ", board_index, " power - status=", status, " safety_relay=", safety_relay_status); if (safety_relay_status) { std::cout << std::endl << " "; + // check power status if (Board[board_index]->GetPowerStatus()) { - std::cout << " mv_good=ok"; - logfile << " mv_good=ok"; + logDebugInfo(" mv_good=ok"); bool board_failed = false; - std::cout << " amp_power="; - logfile << " amp_power="; + logDebugInfo(" amp_power="); + // check amp status for (int channel_index = 0; channel_index < NUM_CHANNEL_PER_FPGA; channel_index++) { if (Board[board_index]->GetAmpStatus(channel_index)) { - std::cout << 1; - logfile << 1; + logDebugInfo(1); } else { - std::cout << 0; - logfile << 0; + logDebugInfo(0); board_failed = true; } } if (board_failed) { - std::cout << " amp_power=bad"; - logfile << " amp_power=bad"; - result = fatal_fail; - std::cout << FAIL << std::endl; - logfile << "... FAIL" << std::endl; + logDebugInfo(" amp_power=bad"); + logFail(result, fatal_fail); } else { - std::cout << PASS << std::endl; - logfile << "... PASS" << std::endl; + logPass(); } } else { - std::cout << " mv_good=bad"; - logfile << " mv_good=bad"; + logDebugInfo(" mv_good=bad"); mv_good_fail_count++; - result = fatal_fail; - std::cout << FAIL << std::endl; - logfile << "... FAIL" << std::endl; + logFail(result, fatal_fail); } } else { - result = fatal_fail; - std::cout << FAIL << std::endl; - logfile << "... FAIL" << std::endl; + logFail(result, fatal_fail); } } @@ -412,6 +559,29 @@ Result TestMotorPowerControl(AmpIO **Board, BasePort *Port) { return result; } +/**************************************************************** +* @brief: Function to test motor current operation. +* +* @details: This function goes through each FPGA and each channel on the FPGA and +* tests the motor current operation. It first tests that the motor has no current +* when it is meant to be off. It then sets the motor current and reads that the +* motor current is as expected. Then it tests if the current drives the actual +* analog devices (the analog device performance is read from the potentiometers) +* as expected. +* +* @param AmpIO (**Board): Array of board objects to have this function performed on +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* +* @return testResult (Result): result object indicating pass, fail, or fatal_fail +* status of the test +* +* @note: This function outputs debug information to standard out and the logfile. It +* outputs the board, amplifier channel, expected and actual motor current values. +* @note: This function has extra logic to test if the DB9 cables might be crossed or +* even not plugged in. +* +****************************************************************/ Result TestPowerAmplifier(AmpIO **Board, BasePort *Port) { int crossed_db9_error_count = 0; Result result = pass; @@ -423,6 +593,7 @@ Result TestPowerAmplifier(AmpIO **Board, BasePort *Port) { Port->ReadAllBoards(); + // check for no current if (std::fabs((long) Board[0]->GetAnalogInput(0) - (long) POT_TEST_ADC_COUNT[0] / 2) < POT_TEST_ADC_ERROR_TOLERANCE) { std::cout << COLOR_ERROR << "(unexpected current detected on channel 0)" << COLOR_OFF << std::endl; @@ -430,11 +601,15 @@ Result TestPowerAmplifier(AmpIO **Board, BasePort *Port) { return fatal_fail; } + // write motor current Board[board_index]->SetMotorCurrent(channel_index, POWER_AMP_TEST_DAC); Port->WriteAllBoards(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); Port->ReadAllBoards(); + + // check motor current is within range of expected motor current + // also check motor current is driving analog inputs correctly auto motor_current = Board[board_index]->GetMotorCurrent(channel_index); bool amp_working = std::fabs((long) motor_current - (long) POWER_AMP_TEST_DAC) < POWER_AMP_TEST_TOLERANCE; @@ -442,6 +617,7 @@ Result TestPowerAmplifier(AmpIO **Board, BasePort *Port) { std::fabs((long) Board[0]->GetAnalogInput(0) - (long) POT_TEST_ADC_COUNT[0] / 2) < POT_TEST_ADC_ERROR_TOLERANCE; + // extra debugging info if needed (if db0 cables are crossed) if (board_index == 0 && channel_index == 0 && amp_working && !channel_0_load_driven) { crossed_db9_error_count++; } @@ -454,20 +630,13 @@ Result TestPowerAmplifier(AmpIO **Board, BasePort *Port) { crossed_db9_error_count++; } - std::cout << std::hex << "board " << board_index << " amp channel " << channel_index << " - " - << " expected=" - << POWER_AMP_TEST_DAC << " measured=" << motor_current; - logfile << std::hex << "board " << board_index << " amp channel " << channel_index << " - " - << " expected=" - << POWER_AMP_TEST_DAC << " measured=" << motor_current; + logDebugInfo(std::hex, "board ", board_index, " amp channel ", channel_index, " - ", " expected=", + POWER_AMP_TEST_DAC, " measured=", motor_current); if (!amp_working) { - result = fail; - std::cout << FAIL << std::endl; - logfile << "... FAIL" << std::endl; + logFail(result, fail); } else { - std::cout << PASS << std::endl; - logfile << "... PASS" << std::endl; + logPass(); } Board[board_index]->SetMotorCurrent(channel_index, 0x8000); @@ -491,7 +660,24 @@ Result TestPowerAmplifier(AmpIO **Board, BasePort *Port) { } +/**************************************************************** +* @brief: Function to test the Dallas Chip used in the dVRK. +* +* @details: This function checks whether the dallas chip can be read from and if it has +* the proper family code (checks it is the expected chip). +* +* @param AmpIO (**Board): Array of board objects to have this function performed on +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* +* @return testResult (Result): result object indicating pass, fail, or fatal_fail +* status of the test +* +* @note: This function outputs debug information to standard out and the logfile, but +* only what the test failed on. +****************************************************************/ Result TestDallas(AmpIO **Board, BasePort *Port) { + Result result = pass; std::cout << "dallas "; logfile << "dallas "; AmpIO *board; @@ -502,24 +688,23 @@ Result TestDallas(AmpIO **Board, BasePort *Port) { } unsigned char buffer[2048]; uint32_t status = 0; + + // read from the Dallas chip and read family code bool ret = board->DallasReadMemory(0, (unsigned char *) buffer, sizeof(buffer)); board->DallasReadStatus(status); unsigned char family_code = static_cast((status&0xFF000000)>>24); + + // check everything is as expected if (!ret) { - std::cout << FAIL << std::endl; + logFail(result, fail); std::cout << COLOR_ERROR << "(Can't talk to DS2480B. Check SCSI cable.)" << COLOR_OFF << std::endl; - logfile << "... FAIL" << std::endl; - return fail; } else if (family_code != 0x0B) { - std::cout << FAIL << std::endl; - std::cout << COLOR_ERROR << "(Can talk to DS2480B but not DS2505. Check dMIB jumper J42.)" << COLOR_OFF << std::endl; - logfile << "... FAIL" << std::endl; - return fail; + logFail(result, fail); + std::cout << COLOR_ERROR << "(Can talk to DS2480B but not DS2505. Check dMIB jumper J42.)" << COLOR_OFF << std::endl; } else { - std::cout << PASS << std::endl; - logfile << "... PASS" << std::endl; - return pass; + logPass(); } + return result; } //*********************************** Main ******************************************** @@ -531,53 +716,22 @@ void PrintDebugStream(std::stringstream &debugStream) debugStream.str(""); } -int main(int argc, char **argv) -{ - int board1 = BoardIO::MAX_BOARDS; - int board2 = BoardIO::MAX_BOARDS; - - std::string portDescription = BasePort::DefaultPort(); - - for (int i = 1; i < argc; i++) { - if ((argv[i][0] == '-') && (argv[i][1] == 'p')) { - // -p option can be -pN, -pfwN, or -pethN, where N - // is the port number. -pN is equivalent to -pfwN - // for backward compatibility. - portDescription = argv[i]+2; - } - } - - std::stringstream debugStream(std::stringstream::out|std::stringstream::in); - BasePort *Port = PortFactory(portDescription.c_str(), debugStream); - if (!Port || !Port->IsOK()) { - PrintDebugStream(debugStream); - std::cerr << "Failed to initialize " << Port->GetPortTypeString() << std::endl; - std::cerr << "Press [Enter] to exit" << std::endl; - std::cin.ignore(); - return -1; - } - - // Now that Port is initialized, look for dVRK Controllers - std::vector ControllerList = GetControllerList(Port); - if (ControllerList.size() > 0) { - std::cout << "Controllers on " << Port->GetPortTypeString() << ":" << std::endl; - std::vector::const_iterator it; - for (it = ControllerList.begin(); it != ControllerList.end(); it++) { - std::cout << " " << ControllerTypeString[it->armType] << ", " << Port->GetHardwareVersionString(it->board_id); - if (it->hwVersion == QLA1_String) - std::cout << ", boards " << it->board_id << ", " << it->board_id+1 << std::endl; - else - std::cout << ", board " << it->board_id << std::endl; - } - std::cout << std::endl; - } - else { - std::cout << "No controllers found on " << Port->GetPortTypeString() << ", exiting" << std::endl; - std::cout << "Press [Enter] to exit" << std::endl; - std::cin.ignore(); - return -1; - } - +/**************************************************************** +* @brief: Function to check for problems with controllers. +* +* @details: This function checks for any issues with the controllers in the dVRK +* before running the tests. +* +* @param ControllerList (std::vector): A vector of the controllers +* that were found in the dVRK and information about them (The +* ControllerInfo struct can be found in this file). +* +* @return int: this int indicates if there was an error found. -1 indicates that an +* error was found with the controllers and 0 indicates no error +* +* @note: This function also outputs to standard out what the issue found was. +****************************************************************/ +int controllerErrorCheck(std::vector &ControllerList) { if (ControllerList.size() != 1) { std::cout << "More than one controller found, please remove all controllers except unit under test" << std::endl; std::cout << "Press [Enter] to exit" << std::endl; @@ -614,54 +768,97 @@ int main(int argc, char **argv) return -1; } - std::cout << "*********** dVRK Classic Controller Test ***********" << std::endl; - std::cout << "This program tests dVRK Classic controllers using a special test board." << std::endl; + return 0; +} - // If user did not specify board id, use the one obtained by scanning the bus - // (program no longer supports user specification of board id). - if (board1 == BoardIO::MAX_BOARDS) { - board1 = ControllerList[0].board_id; - if (ControllerList[0].hwVersion == QLA1_String) { - board2 = board1+1; - is_dqla = false; - NUM_FPGA = 2; - NUM_CHANNEL_PER_FPGA = 4; +/**************************************************************** +* @brief: Function to setup the port and controllers. +* +* @details: This function does the initial setup of the port and controllers in +* the dVRK, including calling the function to check for any errors with the +* controllers. +* +* @param argc (int): number of command line arguments in the terminal. +* @param argv (char**): pointer to array of strings of the command line arguments +* provided in the terminal. +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* @param ControllerList (std::vector): A vector of the controllers +* that were found in the dVRK and information about them. +* +* @return int: this int indicates if there was an error found in the setup. -1 +* indicates that an error was found and 0 indicates no error. +* +* @note: This function also outputs to standard out or standard error some debug +* information on if the port failed to initialize or no controllers are found. +****************************************************************/ +int portAndControllerSetup(int argc, char **argv, BasePort **Port, std::vector &ControllerList) { + std::string portDescription = BasePort::DefaultPort(); + + for (int i = 1; i < argc; i++) { + if ((argv[i][0] == '-') && (argv[i][1] == 'p')) { + portDescription = argv[i]+2; } } - std::vector BoardList; - BoardList.push_back(new AmpIO(board1)); - Port->AddBoard(BoardList[0]); - if (!is_dqla) { - BoardList.push_back(new AmpIO(board2)); - Port->AddBoard(BoardList[1]); + std::stringstream debugStream(std::stringstream::out|std::stringstream::in); + *Port = PortFactory(portDescription.c_str(), debugStream); + if (!(*Port) || !(*Port)->IsOK()) { + PrintDebugStream(debugStream); + std::cerr << "Failed to initialize " << (*Port)->GetPortTypeString() << std::endl; + std::cerr << "Press [Enter] to exit" << std::endl; + std::cin.ignore(); + return -1; } - Port->ReadAllBoards(); - auto controller_sn = BoardList[0]->GetFPGASerialNumber(); - if (controller_sn.empty()) { - controller_sn = "unknown"; + // Now that Port is initialized, look for dVRK Controllers + ControllerList = GetControllerList(*Port); + if (ControllerList.size() > 0) { + std::cout << "Controllers on " << (*Port)->GetPortTypeString() << ":" << std::endl; + std::vector::const_iterator it; + for (it = ControllerList.begin(); it != ControllerList.end(); it++) { + std::cout << " " << ControllerTypeString[it->armType] << ", " << (*Port)->GetHardwareVersionString(it->board_id); + if (it->hwVersion == QLA1_String) + std::cout << ", boards " << it->board_id << ", " << it->board_id+1 << std::endl; + else + std::cout << ", board " << it->board_id << std::endl; + } + std::cout << std::endl; + } + else { + std::cout << "No controllers found on " << (*Port)->GetPortTypeString() << ", exiting" << std::endl; + std::cout << "Press [Enter] to exit" << std::endl; + std::cin.ignore(); + return -1; } - - std::stringstream filename; - auto t = std::time(nullptr); - auto tm = *std::localtime(&t); - #ifdef _MSC_VER // Windows - _mkdir("dVRK_test_results"); - #else - mkdir("dVRK_test_results", 0777); - #endif - filename << "dVRK_test_results/dVRK_" << controller_sn << "_" << std::put_time(&tm, "%Y-%m-%d-%H-%M-%S") << ".txt"; - logfile.open(filename.str(), std::fstream::out); + return controllerErrorCheck(ControllerList); +} - // Check motor supply voltage +/**************************************************************** +* @brief: Function to check motor supply voltages of the dVRK. +* +* @details: This function measures the motor supply voltages on the connected +* QLA boards and compares them against the expected nominal values based on the +* controller type. It handles both DQLA (dual QLA) and standard QLA configurations. +* +* @param BasePort (*Port): Communication interface object that provides access to the +* physical connection to the controller boards +* @param BoardList (std::vector): Vector of AmpIO board objects +* representing the hardware interface to the controller boards +* @param board1 (int): The board ID of the primary FPGA board +* +* @return none +* +* @note: This function outputs the measured voltage and expected nominal voltage to +* standard out and the logfile. +****************************************************************/ +void checkMotorSupplyVoltage(BasePort *Port, std::vector BoardList, int board1) { double V[2] = { 0.0, 0.0}; // QLA 1,2 measured motor supply voltage if (is_dqla) { V[0] = MeasureMotorSupplyVoltage(Port, BoardList[0], 1); V[1] = MeasureMotorSupplyVoltage(Port, BoardList[0], 2); - } - else { + } else { V[0] = MeasureMotorSupplyVoltage(Port, BoardList[0]); V[1] = MeasureMotorSupplyVoltage(Port, BoardList[1]); } @@ -675,35 +872,37 @@ int main(int argc, char **argv) bool V_mismatch = false; for (int v = 0; v < 2; v++) { if (V[v] > 0.0) { - std::cout << "QLA " << (v+1) << " Motor Supply Voltage: " << V[v] - << " (should be " << VNom[v] << ")" << std::endl; - logfile << "QLA " << (v+1) << " Motor Supply Voltage: " << V[v] - << " (should be " << VNom[v] << ")" << std::endl; + logDebugInfo("QLA ", (v+1), " Motor Supply Voltage: ", V[v], " (should be ", VNom[v], ")", "\n"); if (fabs(V[v]-VNom[v]) > 0.1*VNom[v]) V_mismatch = true; } } if (V_mismatch) { - std::cout << "WARNING: Motor supply voltages do not match controller type" << std::endl; - logfile << "WARNING: Motor supply voltages do not match controller type" << std::endl; + logDebugInfo("WARNING: Motor supply voltages do not match controller type", "\n"); std::cout << "Press [Enter] to continue. Press [Ctrl-C] to exit." << std::endl; std::cin.ignore(); } +} - for (int i = 0; i < NUM_CHANNEL_PER_FPGA; i++) { - for (auto &j : BoardList) - j->WriteEncoderPreload(i, 0x1000 * i + 0x1000); - } - - std::cout << "Board ID selected: Board 0=" << board1; - if (!is_dqla) std::cout << " Board 1=" << board2; - std::cout << std::endl; - logfile << "Board ID selected: Board 0=" << board1; - if (!is_dqla) logfile << " Board 1=" << board2; - logfile << std::endl; - - bool all_pass = true; - +/**************************************************************** +* @brief: Function to execute all test functions in sequence. +* +* @details: This function creates a vector of test function pointers and runs each +* test in order. It records the results of each test and handles fatal failures by +* immediately terminating the test sequence. +* +* @param Port (*BasePort): Communication interface object that provides access to the +* physical connection to the controller boards +* @param BoardList (std::vector): Vector of AmpIO board objects representing +* the hardware interface to the controller boards +* +* @return all_pass (bool): Returns true if all tests passed, false if any test failed +* +* @note: This function logs the result of each test section to the logfile. +* @note: If any test results in a fatal failure, the function returns immediately without +* running the remaining tests. +****************************************************************/ +bool runTests(BasePort *Port, std::vector BoardList) { std::vector test_functions; test_functions.push_back(TestSerialNumberAndVersion); test_functions.push_back(TestEncoders); @@ -713,19 +912,115 @@ int main(int argc, char **argv) test_functions.push_back(TestMotorPowerControl); test_functions.push_back(TestPowerAmplifier); + bool all_pass = true; + for (auto test_function : test_functions) { auto result = test_function(&BoardList[0], Port); - std::cout << std::endl; logfile << "Section result: " << (result == pass ? "PASS" : "FAIL") << std::endl; - logfile << std::endl; + logDebugInfo("\n"); if (result == fatal_fail) { - all_pass = false; - break; + return false; } else if (result == fail) { all_pass = false; } } + return all_pass; +} + +/**************************************************************** +* @brief: Function to set up the AmpIO board objects based on controller configuration. +* +* @details: This function creates and initializes the AmpIO board objects based on +* the detected controller type (DQLA or standard QLA). For standard QLA, it sets up +* two separate board objects, while for DQLA it sets up a single board object. +* +* @param Port (BasePort**): Pointer to Communication interface object that provides +* access to the physical connection to the controller boards +* @param board1 (int&): Reference to the board ID of the primary FPGA board +* @param board2 (int&): Reference to the board ID of the secondary FPGA board (used only +* for standard QLA configuration) +* +* @return BoardList (std::vector): Vector of initialized AmpIO board objects +* +* @note: If board1 is set to MAX_BOARDS, indicating that the user did not specify a +* board ID, this function uses the board ID obtained from scanning the bus. +* @note: The function also updates global variables is_dqla, NUM_FPGA, and +* NUM_CHANNEL_PER_FPGA based on the detected controller type. +****************************************************************/ +std::vector boardSetUp(BasePort **Port, int &board1, int &board2) { + if (board1 == BoardIO::MAX_BOARDS) { + auto ControllerList = GetControllerList(*Port); + board1 = ControllerList[0].board_id; + if (ControllerList[0].hwVersion == QLA1_String) { + board2 = board1+1; + is_dqla = false; + NUM_FPGA = 2; + NUM_CHANNEL_PER_FPGA = 4; + } + } + + std::vector BoardList; + BoardList.push_back(new AmpIO(board1)); + (*Port)->AddBoard(BoardList[0]); + if (!is_dqla) { + BoardList.push_back(new AmpIO(board2)); + (*Port)->AddBoard(BoardList[1]); + } + + return BoardList; +} + +int main(int argc, char **argv) { + int board1 = BoardIO::MAX_BOARDS; + int board2 = BoardIO::MAX_BOARDS; + BasePort *Port; + std::vector ControllerList; + + int setup_result = portAndControllerSetup(argc, argv, &Port, ControllerList); + if (setup_result != 0) { + std::cout << "Error in port and controller setup" << std::endl; + return setup_result; + } + + std::cout << "*********** dVRK Classic Controller Test ***********" << std::endl; + std::cout << "This program tests dVRK Classic controllers using a special test board." << std::endl; + + std::vector BoardList = boardSetUp(&Port, board1, board2); + + Port->ReadAllBoards(); + auto controller_sn = BoardList[0]->GetFPGASerialNumber(); + if (controller_sn.empty()) { + controller_sn = "unknown"; + } + + std::stringstream filename; + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + #ifdef _MSC_VER // Windows + _mkdir("dVRK_test_results"); + #else + mkdir("dVRK_test_results", 0777); + #endif + filename << "dVRK_test_results/dVRK_" << controller_sn << "_" << std::put_time(&tm, "%Y-%m-%d-%H-%M-%S") << ".txt"; + logfile.open(filename.str(), std::fstream::out); + + // Check motor supply voltage + checkMotorSupplyVoltage(Port, BoardList, board1); + + for (int i = 0; i < NUM_CHANNEL_PER_FPGA; i++) { + for (auto &j : BoardList) + j->WriteEncoderPreload(i, 0x1000 * i + 0x1000); + } + + logDebugInfo("Board ID selected: Board 0=", board1); + if (!is_dqla) { + logDebugInfo(" Board 1=", board2); + } + logDebugInfo("\n"); + + bool all_pass = runTests(Port, BoardList); + if (all_pass) { std::cout << COLOR_GOOD << "PASS" << COLOR_OFF << ": all tests passed." <WritePowerEnable(false); j->WriteSafetyRelay(false); @@ -744,5 +1038,4 @@ int main(int argc, char **argv) std::cin.ignore(); return all_pass ? 0 : -1; - } diff --git a/tests/mainEth1394.cpp b/tests/fpgatest.cpp similarity index 77% rename from tests/mainEth1394.cpp rename to tests/fpgatest.cpp index dabeca7..65d03d7 100644 --- a/tests/mainEth1394.cpp +++ b/tests/fpgatest.cpp @@ -472,7 +472,7 @@ bool InitEthernet(AmpIO &Board, unsigned int eth_port) return true; } -AmpIO *SelectBoard(const std::string &portName, std::vector boardList, AmpIO *curBoard) +AmpIO *SelectBoard(const std::string &portName, const std::vector &boardList, AmpIO *curBoard) { AmpIO *newBoard = curBoard; if (boardList.size() > 1) { @@ -539,7 +539,7 @@ void TestDacRW(BasePort *port, unsigned char boardNum) std::cout << "Failed to write block data" << std::endl; } -void WriteAllBoardsTest(BasePort *port, std::vector &boardList) +void WriteAllBoardsTest(BasePort *port, const std::vector &boardList) { std::cout << "Protocol: " << port->GetProtocol() << std::endl; for (unsigned int j = 0; j < boardList.size(); j++) { @@ -562,6 +562,7 @@ void ContinuousReadTest(BasePort *port, unsigned char boardNum) size_t readFailures = 0; size_t compareFailures = 0; unsigned long count = 0; + while (!done) { count++; read_data = 0; @@ -588,7 +589,7 @@ void ContinuousReadTest(BasePort *port, unsigned char boardNum) } } -void ContinuousWriteTest(BasePort *ethPort, unsigned char boardNum) +void ContinuousWriteTest(BasePort *port, unsigned char boardNum) { bool done = false; quadlet_t read_data; @@ -604,11 +605,11 @@ void ContinuousWriteTest(BasePort *ethPort, unsigned char boardNum) read_data = -1; write_data++; count++; - if (!ethPort->WriteQuadlet(boardNum, regnum, write_data)) + if (!port->WriteQuadlet(boardNum, regnum, write_data)) writeFailures++; else { Amp1394_Sleep(50*1e-6); // sleep 50 us - if (ethPort->ReadQuadlet(boardNum, regnum, read_data)) { + if (port->ReadQuadlet(boardNum, regnum, read_data)) { if (memcmp((void *)&read_data, (void *)&write_data, 4) == 0) success++; else { @@ -928,7 +929,7 @@ void TestBlockWrite(unsigned int wlen_max, AmpIO *wboard, AmpIO *rboard) } } -void TestWaveform(AmpIO *board) +void TestWaveform(AmpIO *board, size_t numReads = 1) { const unsigned int WLEN = 256; quadlet_t waveform[WLEN]; @@ -960,20 +961,25 @@ void TestWaveform(AmpIO *board) return; } Amp1394_Sleep(0.05); - std::cout << "Reading data" << std::endl; - if (!board->ReadWaveformTable(waveform_read, 0, WLEN)) { - std::cout << "ReadWaveformTable failed" << std::endl; - return; - } - for (i = 0; i < WLEN; i++) { - if (waveform_read[i] != waveform[i]) { - std::cout << "Mismatch at quadlet " << i << ", read " << std::hex - << waveform_read[i] << ", expected " << waveform[i] - << std::dec << std::endl; + for (size_t n = 0; n < numReads; n++) { + std::cout << "Reading data"; + if (numReads > 1) + std::cout << ": iteration " << n; + std::cout << std::endl; + if (!board->ReadWaveformTable(waveform_read, 0, WLEN)) { + std::cout << "ReadWaveformTable failed" << std::endl; return; } + for (i = 0; i < WLEN; i++) { + if (waveform_read[i] != waveform[i]) { + std::cout << "Mismatch at quadlet " << i << ", read " << std::hex + << waveform_read[i] << ", expected " << waveform[i] + << std::dec << std::endl; + return; + } + } + std::cout << "Pattern verified!" << std::endl; } - std::cout << "Pattern verified!" << std::endl; #if 0 // Following code will actually generate waveform on DOUT lines board->WriteDigitalOutput(0x0f,0x00); @@ -1136,6 +1142,7 @@ int main(int argc, char **argv) int port = 0; std::string IPaddr(ETH_UDP_DEFAULT_IP); bool fwBridge = false; + bool isVerbose = false; if (argc > 1) { for (int i = 1; i < argc; i++) { @@ -1148,10 +1155,14 @@ int main(int argc, char **argv) useEthernet = false; } } + else if ((argv[i][0] == '-') && (argv[i][1] == 'v')) { + isVerbose = true; + } else { - std::cerr << "Usage: eth1394test [-pP]" << std::endl + std::cerr << "Usage: eth1394test [-pP] [-v]" << std::endl << " where P = port number (default 0)" << std::endl - << " can also specify -pethP or -pudp" << std::endl; + << " can also specify -pethP or -pudp" << std::endl + << " -v verbose output (ZynqEmioPort)" << std::endl; return 0; } @@ -1183,7 +1194,9 @@ int main(int argc, char **argv) std::string EthPortString; std::string FwPortString; + std::string curPortString; + bool FwPortIsZynq = false; #if Amp1394_HAS_RAW1394 FwPort = new FirewirePort(port, std::cout); if (!FwPort->IsOK()) { @@ -1199,11 +1212,15 @@ int main(int argc, char **argv) } } #elif Amp1394_HAS_EMIO - FwPort = new ZynqEmioPort(port, std::cout); - if (!FwPort->IsOK()) { + ZynqEmioPort *ZynqPort; + ZynqPort = new ZynqEmioPort(port, std::cout); + if (!ZynqPort->IsOK()) { std::cout << "Failed to initialize Zynq EMIO port" << std::endl; return 0; } + ZynqPort->SetVerbose(isVerbose); + FwPort = ZynqPort; + FwPortIsZynq = true; // Zynq EMIO port always has one node (0) unsigned int bnum = FwPort->GetBoardId(0); if (bnum < BoardIO::MAX_BOARDS) { @@ -1216,6 +1233,9 @@ int main(int argc, char **argv) if (FwBoardList.size() > 0) { curBoardFw = FwBoardList[0]; FwPortString = FwPort->GetPortTypeString(); + curBoard = curBoardFw; + curPort = FwPort; + curPortString = FwPortString; } EthBasePort *EthPort = 0; @@ -1244,8 +1264,15 @@ int main(int argc, char **argv) EthBoardList.push_back(board); } } - if (EthBoardList.size() > 0) + if (EthBoardList.size() > 0) { curBoardEth = EthBoardList[0]; + if (!curBoard) { + // If curBoard not already set to Firewire + curBoard = curBoardEth; + curPort = EthPort; + curPortString = EthPortString; + } + } } else std::cout << "Failed to initialize Ethernet port" << std::endl; @@ -1255,7 +1282,12 @@ int main(int argc, char **argv) } if (!EthPort && !FwPort) { - std::cout << "No ports created - existing" << std::endl; + std::cout << "No ports created - exiting" << std::endl; + return -1; + } + + if (!curBoardEth && !curBoardFw) { + std::cout << "No boards found - exiting" << std::endl; return -1; } @@ -1276,102 +1308,68 @@ int main(int argc, char **argv) while (!done) { - // Set curBoard to curBoardFw if it is valid; otherwise curBoardEth. - // Also set curPort to be consistent with curBoard. - if (curBoardFw) { - curBoard = curBoardFw; - curPort = FwPort; - } - else { - curBoard = curBoardEth; - curPort = EthPort; - } + unsigned char curBoardNum = curBoard->GetBoardId(); + unsigned char EthBoardNum = 0; + unsigned char FwBoardNum = 0; + unsigned int fpga_ver = curBoard->GetFpgaVersionMajor(); + double clkPeriod = curBoard->GetFPGAClockPeriod(); - unsigned char boardNumEth = 0; - unsigned char boardNumFw = 0; - if (curBoardEth) - boardNumEth = curBoardEth->GetBoardId(); - if (curBoardFw) - boardNumFw = curBoardFw->GetBoardId(); - - std::cout << std::endl << "Ethernet Test Program" << std::endl; + std::cout << std::endl << "FPGA Test Program" << std::endl; if (curBoardFw) { + FwBoardNum = curBoardFw->GetBoardId(); std::cout << " " << FwPortString << " board: " - << static_cast(boardNumFw) << std::endl; + << static_cast(FwBoardNum); + if (curPort == FwPort) + std::cout << " <-- active"; + std::cout << std::endl; } if (curBoardEth) { - std::cout << " " << EthPortString << " board: " << static_cast(boardNumEth); + EthBoardNum = curBoardEth->GetBoardId(); + std::cout << " " << EthPortString << " board: " + << static_cast(EthBoardNum); + if (curPort == EthPort) + std::cout << " <-- active"; std::cout << std::endl; } + std::cout << " 0) Quit" << std::endl; - if (curBoardEth) { - std::cout << " 1) Quadlet write (power/relay toggle) to board via " - << EthPortString << std::endl; - std::cout << " 2) Quadlet read from board via " - << EthPortString << std::endl; - } - if (curBoardEth && curBoardFw) { - std::cout << " 3) Block read from board via " << EthPortString - << " and " << FwPortString << std::endl; - std::cout << " 4) Block write to board via " << EthPortString - << " and " << FwPortString << std::endl; - } - else if (curBoardEth) { - std::cout << " 3) Block read from board via " << EthPortString << std::endl; - std::cout << " 4) Block write to board via " << EthPortString << std::endl; - } - else if (curBoardFw) { - std::cout << " 3) Block read from board via " << FwPortString << std::endl; - std::cout << " 4) Block write to board via " << FwPortString << std::endl; - } - if (curBoardFw) { - std::cout << " 5) Ethernet port status" << std::endl; + std::cout << " 1) Quadlet write (power/relay toggle) to board" << std::endl; + std::cout << " 2) Quadlet read from board" << std::endl; + std::cout << " 3) Block read from board" << std::endl; + std::cout << " 4) Block write to board" << std::endl; + std::cout << " 5) Ethernet port status" << std::endl; + if (curPort == FwPort) std::cout << " 6) Initialize Ethernet port" << std::endl; - } - if (curBoard) - std::cout << " 7) Ethernet status info" << std::endl; - if (EthPort) - std::cout << " 8) Multicast quadlet read via " << EthPortString << std::endl; - if (curBoardEth) - std::cout << " a) WriteAllBoards test" << std::endl; - if (curBoardEth || curBoardFw) - std::cout << " B) BlockWrite test (Waveform table)" << std::endl; - if ((EthBoardList.size() > 1) || (FwBoardList.size() > 1)) + std::cout << " 7) Ethernet status info" << std::endl; + if (FwPortIsZynq || (curPort == EthPort)) + std::cout << " 8) Multicast quadlet read" << std::endl; + std::cout << " a) WriteAllBoards test" << std::endl; + std::cout << " B) BlockWrite test (Waveform table)" << std::endl; + if (((curPort == EthPort) && (EthBoardList.size() > 1)) || + ((curPort == FwPort) && (FwBoardList.size() > 1))) std::cout << " b) Change board" << std::endl; std::cout << " C) Compute Configuration ROM CRC" << std::endl; - if (curBoardEth) { - std::cout << " c) Continuous test (quadlet reads)" << std::endl; - std::cout << " d) Continuous write test (quadlet write)" << std::endl; - } - if (curBoardFw) - std::cout << " e) Read RXFCTR packet count" << std::endl; - if (curBoardEth || curBoardFw) - std::cout << " f) Print Firewire PHY registers" << std::endl; - if (curBoardFw) { - std::cout << " i) Read IPv4 address via " << FwPortString << std::endl; - std::cout << " I) Clear IPv4 address via " << FwPortString << std::endl; - } - if (curBoard) { - std::cout << " m) Initialize and test I/O Expander (QLA 1.5+)" << std::endl; - } + std::cout << " c) Continuous test (quadlet reads)" << std::endl; + std::cout << " d) Continuous write test (quadlet write)" << std::endl; + if ((fpga_ver == 2) && (curPort == FwPort)) + std::cout << " e) Read RXFCTR packet count (FPGA V2)" << std::endl; + std::cout << " f) Print Firewire PHY registers" << std::endl; + std::cout << " i) Read IPv4 address" << std::endl; + std::cout << " I) Clear IPv4 address" << std::endl; + std::cout << " m) Initialize and test I/O Expander (QLA 1.5+)" << std::endl; + if (curBoardFw && curBoardEth) + std::cout << " p) Toggle port (" << FwPortString << " or " << EthPortString << ")" << std::endl; std::cout << " P) Compute Ethernet PHY IDs" << std::endl; std::cout << " r) Check Firewire bus generation and rescan if needed" << std::endl; -#if Amp1394_HAS_RAW1394 - if (curBoardFw) + if (!FwPortIsZynq && (curPort == FwPort)) std::cout << " R) Read Firewire Configuration ROM" << std::endl; -#endif - if (curBoardEth || curBoardFw) - std::cout << " t) Run timing analysis" << std::endl; - if (curBoard) { - std::cout << " v) Measure motor power supply voltage (QLA 1.5+)" << std::endl; - std::cout << " w) Test waveform buffer" << std::endl; - std::cout << " x) Read Ethernet debug data" << std::endl; - std::cout << " X) Clear Ethernet errors" << std::endl; - } - if (curBoardEth) - std::cout << " y) Read Firewire data via Ethernet" << std::endl; - if (curBoardFw) - std::cout << " z) Check Ethernet initialization" << std::endl; + std::cout << " t) Run timing analysis" << std::endl; + std::cout << " v) Measure motor power supply voltage (QLA 1.5+)" << std::endl; + std::cout << " w) Test waveform buffer" << std::endl; + std::cout << " x) Read Ethernet debug data" << std::endl; + std::cout << " X) Clear Ethernet errors" << std::endl; + std::cout << " y) Read Firewire debug data" << std::endl; + std::cout << " z) Check Ethernet initialization" << std::endl; std::cout << "Select option: "; int c = getchar(); @@ -1389,137 +1387,127 @@ int main(int argc, char **argv) break; case '1': // Write quadlet from PC to FPGA - if (curBoardEth) { - if (write_data == 0x000f0000) - write_data = 0x000a0000; // power/relay off - else - write_data = 0x000f0000; // power/relay on - if (!EthPort->WriteQuadlet(boardNumEth, 0x0, write_data )) - std::cout << "Failed to write quadlet via " << EthPortString << " port" << std::endl; - else - std::cout << "Write data = 0x" << std::hex << write_data << "\n"; - } + if (write_data == 0x000f0000) + write_data = 0x000a0000; // power/relay off + else + write_data = 0x000f0000; // power/relay on + if (!curPort->WriteQuadlet(curBoardNum, 0x0, write_data )) + std::cout << "Failed to write quadlet via " << curPortString << " port" << std::endl; + else + std::cout << "Write data = 0x" << std::hex << write_data << "\n"; break; - case '2': // Read request from PC via Ethernet (note that QuadletReadCallback is called) - if (curBoardEth) { - read_data = 0; - addr = 0x04; // Return QLA1 - if (EthPort->ReadQuadlet(boardNumEth, addr, read_data)) { - std::cout << "Read quadlet data: " << std::hex << read_data << std::endl; - memcpy(buf, (char *)(&read_data), 4); - buf[4] = 0; - std::cout << " as string: " << buf << std::endl; + case '2': // Read request from PC (note that QuadletReadCallback is called) + read_data = 0; + addr = 0x04; // Return QLA1 + if (curPort->ReadQuadlet(curBoardNum, addr, read_data)) { + std::cout << "Read quadlet data: " << std::hex << read_data << std::endl; + memcpy(buf, (char *)(&read_data), 4); + buf[4] = 0; + std::cout << " as string: " << buf << std::endl; + if (curPort == EthPort) { std::cout << "FPGA Recv time (us): " << EthPort->GetFpgaReceiveTime()*1.0e6 << ", FPGA Total time (us): " << EthPort->GetFpgaTotalTime()*1.0e6 << std::endl; } - else - std::cout << "Failed to read quadlet via " << EthPortString << " port" << std::endl; } + else + std::cout << "Failed to read quadlet via " << curPortString << " port" << std::endl; break; case '3': - if (curBoardFw || curBoardEth) { - memset(fw_block_data, 0, sizeof(fw_block_data)); - if (curBoardFw) { - if (!FwPort->ReadBlock(boardNumFw, 0, fw_block_data, sizeof(fw_block_data))) - std::cout << "Failed to read block data via " << FwPortString << " port" << std::endl; - } - memset(eth_block_data, 0, sizeof(eth_block_data)); - if (curBoardEth) { - if (!EthPort->ReadBlock(boardNumEth, 0, eth_block_data, sizeof(eth_block_data))) - std::cout << "Failed to read block data via " << EthPortString << " port" << std::endl; - else - std::cout << "FPGA Recv time (us): " << EthPort->GetFpgaReceiveTime()*1.0e6 - << ", FPGA Total time (us): " << EthPort->GetFpgaTotalTime()*1.0e6 << std::endl; - } - std::cout << " " << FwPortString << " " << EthPortString << std::endl; - for (i = 0; static_cast(i) < sizeof(fw_block_data)/sizeof(quadlet_t); i++) - std::cout << std::setw(2) << std::setfill(' ') << std::dec << i << ": " - << std::setw(8) << std::setfill('0') << std::hex - << bswap_32(fw_block_data[i]) << " " << std::setw(8) << std::setfill('0') - << bswap_32(eth_block_data[i]) << std::endl; - } - break; - - case '4': + memset(fw_block_data, 0, sizeof(fw_block_data)); if (curBoardFw) { - std::cout << "Testing via " << FwPortString << std::endl; - TestDacRW(FwPort, boardNumFw); - break; + if (!FwPort->ReadBlock(FwBoardNum, 0, fw_block_data, sizeof(fw_block_data))) + std::cout << "Failed to read block data via " << FwPortString << " port" << std::endl; } + memset(eth_block_data, 0, sizeof(eth_block_data)); if (curBoardEth) { - std::cout << "Testing via " << EthPortString << std::endl; - TestDacRW(EthPort, boardNumEth); + if (!EthPort->ReadBlock(EthBoardNum, 0, eth_block_data, sizeof(eth_block_data))) + std::cout << "Failed to read block data via " << EthPortString << " port" << std::endl; + else + std::cout << "FPGA Recv time (us): " << EthPort->GetFpgaReceiveTime()*1.0e6 + << ", FPGA Total time (us): " << EthPort->GetFpgaTotalTime()*1.0e6 << std::endl; + } + std::cout << " " << FwPortString << " " << EthPortString << std::endl; + for (i = 0; static_cast(i) < sizeof(fw_block_data)/sizeof(quadlet_t); i++) { + std::cout << std::setw(2) << std::setfill(' ') << std::dec << i << ": " + << std::setw(8) << std::setfill('0') << std::hex + << bswap_32(fw_block_data[i]) << " " << std::setw(8) << std::setfill('0') + << bswap_32(eth_block_data[i]) << std::endl; } break; + case '4': + TestDacRW(curPort, curBoardNum); + break; + case '5': - if (curBoardFw) { - unsigned int fpgaVer = curBoardFw->GetFpgaVersionMajor(); - if (fpgaVer == 2) { - PrintEthernetPhyStatusV2(*curBoardFw); - } - else if (fpgaVer == 3) { - PrintEthernetPhyStatusV3(*curBoardFw, 1); - PrintEthernetPhyStatusV3(*curBoardFw, 2); - } + if (fpga_ver == 2) { + PrintEthernetPhyStatusV2(*curBoard); + } + else if (fpga_ver == 3) { + PrintEthernetPhyStatusV3(*curBoard, 1); + PrintEthernetPhyStatusV3(*curBoard, 2); } break; case '6': - if (curBoardFw) { - unsigned int fpga_ver = curBoardFw->GetFpgaVersionMajor(); + if (curPort == FwPort) { if (fpga_ver == 2) { - InitEthernet(*curBoardFw, 0); + InitEthernet(*curBoard, 0); } else if (fpga_ver == 3) { std::cout << "FPGA V3, Eth1: "; - InitEthernet(*curBoardFw, 1); + InitEthernet(*curBoard, 1); std::cout << "FPGA V3, Eth2: "; - InitEthernet(*curBoardFw, 2); + InitEthernet(*curBoard, 2); } } break; case '7': - if (curBoard) - PrintEthernetStatus(*curBoard); + PrintEthernetStatus(*curBoard); break; - case '8': // Read request via Ethernet multicast - if (EthPort) { + case '8': // Multicast quadlet read (not supported on Firewire) + if (FwPortIsZynq || (curPort == EthPort)) { read_data = 0; addr = 0; // Return status register - if (EthPort->ReadQuadlet(FW_NODE_BROADCAST, addr, read_data)) + if (curPort->ReadQuadlet(FW_NODE_BROADCAST, addr, read_data)) std::cout << "Read quadlet data: " << std::hex << read_data << std::endl; else - std::cout << "Failed to read quadlet via " << EthPortString << " port" << std::endl; + std::cout << "Failed to read quadlet via " << curPortString << " port" << std::endl; } break; case 'a': - if (curBoardEth) - WriteAllBoardsTest(EthPort, EthBoardList); + if (curPort == FwPort) + WriteAllBoardsTest(curPort, FwBoardList); + else + WriteAllBoardsTest(curPort, EthBoardList); break; case 'B': - if (curBoardEth && curBoardFw) { + if (curBoardEth && curBoardFw && (EthBoardNum == FwBoardNum)) { unsigned int wlen_max_eth = (EthPort->GetMaxWriteDataSize()/sizeof(quadlet_t)); unsigned int wlen_max_fw = (FwPort->GetMaxWriteDataSize()/sizeof(quadlet_t)); unsigned int wlen_max = std::min(wlen_max_eth, wlen_max_fw); - std::cout << "Write via " << EthPortString << ", read via " << FwPortString << std::endl; - TestBlockWrite(wlen_max, curBoardEth, curBoardFw); - std::cout << std::endl - <<"Write via " << FwPortString << ", read via " << EthPortString << std::endl; - TestBlockWrite(wlen_max, curBoardFw, curBoardEth); + if (curBoard == curBoardEth) { + std::cout << "Write via " << EthPortString << ", read via " << FwPortString << std::endl; + TestBlockWrite(wlen_max, curBoardEth, curBoardFw); + } + else { + std::cout << std::endl + <<"Write via " << FwPortString << ", read via " << EthPortString << std::endl; + TestBlockWrite(wlen_max, curBoardFw, curBoardEth); + } } - else if (curBoardFw) { + else if (curBoard == curBoardFw) { unsigned int wlen_max = (FwPort->GetMaxWriteDataSize()/sizeof(quadlet_t)); std::cout << "Testing via " << FwPortString << std::endl; TestBlockWrite(wlen_max, curBoardFw, curBoardFw); } - else if (curBoardEth) { + else if (curBoard == curBoardEth) { unsigned int wlen_max = (EthPort->GetMaxWriteDataSize()/sizeof(quadlet_t)); std::cout << "Testing via " << EthPortString << std::endl; TestBlockWrite(wlen_max, curBoardEth, curBoardEth); @@ -1527,13 +1515,14 @@ int main(int argc, char **argv) break; case 'b': - curBoardFw = SelectBoard(FwPortString, FwBoardList, curBoardFw); - curBoardEth = SelectBoard(EthPortString, EthBoardList, curBoardEth); + if ((curPort == FwPort) && (FwBoardList.size() > 1)) + curBoardFw = SelectBoard(FwPortString, FwBoardList, curBoardFw); + else if ((curPort == EthPort) && (EthBoardList.size() > 1)) + curBoardEth = SelectBoard(EthPortString, EthBoardList, curBoardEth); break; case 'c': - if (curBoardEth) - ContinuousReadTest(EthPort, boardNumEth); + ContinuousReadTest(curPort, curBoardNum); break; case 'C': @@ -1541,66 +1530,66 @@ int main(int argc, char **argv) break; case 'd': - if (curBoardEth) - ContinuousWriteTest(EthPort, boardNumEth); + ContinuousWriteTest(curPort, curBoardNum); break; case 'e': - if (curBoardFw) { + if ((fpga_ver == 2) && (curPort == FwPort)) { uint16_t reg; - if (curBoardFw->ReadKSZ8851Reg(0x90, reg)) + if (curBoard->ReadKSZ8851Reg(0x90, reg)) std::cout << "IER = 0x" << std::hex << reg << " "; - if (curBoardFw->ReadKSZ8851Reg(0x92, reg)) + if (curBoard->ReadKSZ8851Reg(0x92, reg)) std::cout << "ISR = 0x" << std::hex << reg << " "; - if (curBoardFw->ReadKSZ8851Reg(0x9C, reg)) + if (curBoard->ReadKSZ8851Reg(0x9C, reg)) std::cout << "RXFCTR = 0x" << std::hex << reg << " "; - if (curBoardFw->ReadKSZ8851Reg(0x7C, reg)) + if (curBoard->ReadKSZ8851Reg(0x7C, reg)) std::cout << "RXFHSR = 0x" << std::hex << reg << " "; - if (curBoardFw->ReadKSZ8851Reg(0x80, reg)) + if (curBoard->ReadKSZ8851Reg(0x80, reg)) std::cout << "TXQCR = 0x" << std::hex << reg << std::endl; } break; case 'f': - if (curBoardFw) { - std::cout << "Firewire PHY data via " << FwPortString << " (board " - << static_cast(boardNumFw) << "):" << std::endl; - PrintFirewirePHY(curBoardFw); - } - if (curBoardEth) { - std::cout << "Firewire PHY data via " << EthPortString << " (board " - << static_cast(boardNumEth) << "):" << std::endl; - PrintFirewirePHY(curBoardEth); - } + std::cout << "Firewire PHY data (board " + << static_cast(curBoardNum) << "):" << std::endl; + PrintFirewirePHY(curBoard); break; case 'i': - if (curBoardFw) - std::cout << "IP Address = " << EthUdpPort::IP_String(curBoardFw->ReadIPv4Address()) << std::endl; + std::cout << "IP Address = " << EthUdpPort::IP_String(curBoard->ReadIPv4Address()) << std::endl; break; case 'I': - if (curBoardFw) { - if (curBoardFw->WriteIPv4Address(0xffffffff)) - std::cout << "Write IP address 255.255.255.255" << std::endl; - else - std::cout << "Failed to write IP address" << std::endl; - } + if (curBoard->WriteIPv4Address(0xffffffff)) + std::cout << "Write IP address 255.255.255.255" << std::endl; + else + std::cout << "Failed to write IP address" << std::endl; break; case 'm': // TEST I/O EXPANDER - if (curBoard) { - if (curBoard->GetHardwareVersion() == DQLA_String) { - DQLA_IOExp_Check(*curBoard, 3); - DQLA_IOExp_Check(*curBoard, 4); - // Could also test I/O expanders on QLA Rev 1.5+ - // In DQLA systems, need to use channels 1 and 2 - // QLA_IOExp_Check(*curBoard, 1); - // QLA_IOExp_Check(*curBoard, 2); + if (curBoard->GetHardwareVersion() == DQLA_String) { + DQLA_IOExp_Check(*curBoard, 3); + DQLA_IOExp_Check(*curBoard, 4); + // Could also test I/O expanders on QLA Rev 1.5+ + // In DQLA systems, need to use channels 1 and 2 + // QLA_IOExp_Check(*curBoard, 1); + // QLA_IOExp_Check(*curBoard, 2); + } + else { + // Test I/O expander on QLA Rev 1.5+ (channel 0) + QLA_IOExp_Check(*curBoard); + } + break; + + case 'p': // Select Ethernet or Firewire/Zynq-EMIO + if (curBoardFw && curBoardEth) { + if (curPort == FwPort) { + curPort = EthPort; + curPortString = EthPortString; } else { - // Test I/O expander on QLA Rev 1.5+ (channel 0) - QLA_IOExp_Check(*curBoard); + curPort = FwPort; + curPortString = FwPortString; } } break; @@ -1615,133 +1604,126 @@ int main(int argc, char **argv) break; case 'r': - if (EthPort && EthPort->IsOK()) - EthPort->CheckFwBusGeneration("EthPort", true); - if (FwPort && (FwPort->IsOK())) - FwPort->CheckFwBusGeneration("FwPort", true); + if (curPort->IsOK()) + curPort->CheckFwBusGeneration(curPortString, true); break; -#if Amp1394_HAS_RAW1394 case 'R': - if (curBoardFw) - ReadConfigROM(FwPort, boardNumFw); + if (!FwPortIsZynq && (curPort == FwPort)) + ReadConfigROM(curPort, curBoardNum); break; -#endif case 't': - if (curBoardEth) - RunTiming(EthPortString, curBoardEth, EthPort, "quadlet read"); - if (curBoardFw) - RunTiming(FwPortString, curBoardFw, 0, "quadlet read"); - if (curBoardEth) - RunTiming(EthPortString, curBoardEth, EthPort, "quadlet write"); - if (curBoardFw) - RunTiming(FwPortString, curBoardFw, 0, "quadlet write"); + if (EthPort && (curBoardNum == EthBoardNum)) { + RunTiming(curPortString, curBoard, EthPort, "quadlet read"); + RunTiming(curPortString, curBoard, EthPort, "quadlet write"); + } + else { + RunTiming(curPortString, curBoard, 0, "quadlet read"); + RunTiming(curPortString, curBoard, 0, "quadlet write"); + } break; case 'v': // Measure power supply voltage (QLA 1.5+) - if (curBoard) { - if (curBoard->GetHardwareVersion() == DQLA_String) { - double V1 = MeasureMotorSupplyVoltage(curPort, curBoard, 1); - double V2 = MeasureMotorSupplyVoltage(curPort, curBoard, 2); - std::cout << "Measured motor supply voltages: " << V1 << ", " << V2 << std::endl; - } - else if (curBoard->GetHardwareVersion() == QLA1_String) { - double V = MeasureMotorSupplyVoltage(curPort, curBoard); - std::cout << "Measured motor supply voltage: " << V << std::endl; - } + if (curBoard->GetHardwareVersion() == DQLA_String) { + double V1 = MeasureMotorSupplyVoltage(curPort, curBoard, 1); + double V2 = MeasureMotorSupplyVoltage(curPort, curBoard, 2); + std::cout << "Measured motor supply voltages: " << V1 << ", " << V2 << std::endl; + } + else if (curBoard->GetHardwareVersion() == QLA1_String) { + double V = MeasureMotorSupplyVoltage(curPort, curBoard); + std::cout << "Measured motor supply voltage: " << V << std::endl; } break; case 'w': - TestWaveform(curBoard); + TestWaveform(curBoard, 10); break; case 'x': - if (curBoard) { - unsigned int fpgaVer = curBoard->GetFpgaVersionMajor(); - double clkPeriod = curBoard->GetFPGAClockPeriod(); - if (curBoard->ReadEthernetData(buffer, 0xc0, 16)) // PacketBuffer - EthBasePort::PrintEthernetPacket(std::cout, buffer, 16); - if (curBoard->ReadEthernetData(buffer, 0, 64)) // FireWire packet - EthBasePort::PrintFirewirePacket(std::cout, buffer, 64); - if (curBoard->ReadEthernetData(buffer, 0x80, 16)) // EthernetIO DebugData - EthBasePort::PrintDebugData(std::cout, buffer, clkPeriod); - if (fpgaVer == 2) { - if (curBoard->ReadEthernetData(buffer, 0x90, 16)) // Low-level DebugData - EthBasePort::PrintDebugDataKSZ(std::cout, buffer, clkPeriod); // KSZ8851 (FPGA V2) - } - else if (fpgaVer == 3) { - if (curBoard->ReadEthernetData(buffer, 0x1a0, 2)) // Low-level DebugData - EthBasePort::PrintDebugDataRTL(std::cout, buffer, "Eth1"); // RTL8211F - if (curBoard->ReadEthernetData(buffer, 0x2a0, 2)) // Low-level DebugData - EthBasePort::PrintDebugDataRTL(std::cout, buffer, "Eth2"); // RTL8211F - if (curBoard->ReadEthernetData(buffer, 0x4a0, 16)) // Low-level DebugData - EthBasePort::PrintDebugDataRTI(std::cout, buffer, clkPeriod); // EthRtInterface (FPGA V3) - uint32_t ethStatus; - curBoard->ReadEthernetStatus(ethStatus); - if (ethStatus & FpgaIO::ETH_STAT_CLK200_OK_V3) - std::cout << "200 MHz clock running" << std::endl; - else - std::cout << "**** 200 MHz clock not running -- initialize PS" << std::endl; - if (ethStatus & FpgaIO::ETH_STAT_CLK125_OK_V3) - std::cout << "125 MHz clock running" << std::endl; - else - std::cout << "**** 125 MHz clock not running -- initialize PS" << std::endl; - } + if (curBoard->ReadEthernetData(buffer, 0xc0, 16)) // PacketBuffer + EthBasePort::PrintEthernetPacket(std::cout, buffer, 16); + if (curBoard->ReadEthernetData(buffer, 0, 64)) // FireWire packet + EthBasePort::PrintFirewirePacket(std::cout, buffer, 64); + if (curBoard->ReadEthernetData(buffer, 0x80, 16)) // EthernetIO DebugData + EthBasePort::PrintDebugData(std::cout, buffer, clkPeriod); + if (fpga_ver == 2) { + if (curBoard->ReadEthernetData(buffer, 0x90, 16)) // Low-level DebugData + EthBasePort::PrintDebugDataKSZ(std::cout, buffer, clkPeriod); // KSZ8851 (FPGA V2) + } + else if (fpga_ver == 3) { + if (curBoard->ReadEthernetData(buffer, 0x1a0, 2)) // Low-level DebugData + EthBasePort::PrintDebugDataRTL(std::cout, buffer, "Eth1"); // RTL8211F + if (curBoard->ReadEthernetData(buffer, 0x2a0, 2)) // Low-level DebugData + EthBasePort::PrintDebugDataRTL(std::cout, buffer, "Eth2"); // RTL8211F + if (curBoard->ReadEthernetData(buffer, 0x4a0, 16)) // Low-level DebugData + EthBasePort::PrintDebugDataRTI(std::cout, buffer, clkPeriod); // EthRtInterface (FPGA V3) + uint32_t ethStatus; + curBoard->ReadEthernetStatus(ethStatus); + if (ethStatus & FpgaIO::ETH_STAT_CLK200_OK_V3) + std::cout << "200 MHz clock running" << std::endl; + else + std::cout << "**** 200 MHz clock not running -- initialize PS" << std::endl; + if (ethStatus & FpgaIO::ETH_STAT_CLK125_OK_V3) + std::cout << "125 MHz clock running" << std::endl; + else + std::cout << "**** 125 MHz clock not running -- initialize PS" << std::endl; + } #if 0 - if ((fpgaVer == 2) && (curBoard->ReadEthernetData(buffer, 0xa0, 32))) { - std::cout << "Initialization Program:" << std::endl; - for (int i = 0; i < 32; i++) { - if (i == 16) - std::cout << "Run Program:" << std::endl; - if (buffer[i] != 0) { - if (buffer[i]&0x02000000) std::cout << " Write "; - else std::cout << " Read "; - std::cout << std::hex << std::setw(2) << std::setfill('0') - << ((buffer[i]&0x00ff0000)>>16) << " "; // address - std::cout << std::hex << std::setw(4) << std::setfill('0') - << (buffer[i]&0x0000ffff); - if (buffer[i]&0x01000000) std::cout << " MOD"; - std::cout << std::endl; - } + if ((fpga_ver == 2) && (curBoard->ReadEthernetData(buffer, 0xa0, 32))) { + std::cout << "Initialization Program:" << std::endl; + for (int i = 0; i < 32; i++) { + if (i == 16) + std::cout << "Run Program:" << std::endl; + if (buffer[i] != 0) { + if (buffer[i]&0x02000000) std::cout << " Write "; + else std::cout << " Read "; + std::cout << std::hex << std::setw(2) << std::setfill('0') + << ((buffer[i]&0x00ff0000)>>16) << " "; // address + std::cout << std::hex << std::setw(4) << std::setfill('0') + << (buffer[i]&0x0000ffff); + if (buffer[i]&0x01000000) std::cout << " MOD"; + std::cout << std::endl; } } - if (curBoard->ReadEthernetData(buffer, 0xe0, 21)) { // ReplyIndex - const uint16_t *packetw = reinterpret_cast(buffer); - std::cout << "ReplyIndex: " << std::endl; - for (int i = 0; i < 41; i++) - std::cout << std::dec << i << ": " << packetw[i] << std::endl; - } -#endif } + if (curBoard->ReadEthernetData(buffer, 0xe0, 21)) { // ReplyIndex + const uint16_t *packetw = reinterpret_cast(buffer); + std::cout << "ReplyIndex: " << std::endl; + for (int i = 0; i < 41; i++) + std::cout << std::dec << i << ": " << packetw[i] << std::endl; + } +#endif break; case 'X': - if (curBoard) { - curBoard->WriteEthernetClearErrors(); - } + curBoard->WriteEthernetClearErrors(); break; case 'y': - if (curBoardEth && (curBoardEth->ReadFirewireData(buffer, 0, 64))) - EthBasePort::PrintFirewirePacket(std::cout, buffer, 64); + if (curPort == EthPort) { + // Can only read Firewire packet from Ethernet interface + if (curBoard->ReadFirewireData(buffer, 0, 64)) { + EthBasePort::PrintFirewirePacket(std::cout, buffer, 64); + } + } + if (curBoard->ReadFirewireData(buffer, 0x200, 4)) { + std::cout << "Firewire debug data:" << std::endl; + FpgaIO::PrintFirewireDebug(std::cout, buffer); + } break; case 'z': - if (curBoardFw) { - unsigned int fpgaVer = curBoardFw->GetFpgaVersionMajor(); - std::cout << "FPGA Rev " << fpgaVer << std::endl; - if (fpgaVer == 2) { - // Check that KSZ8851 registers are as expected - if (!CheckEthernetV2(*curBoardFw)) - PrintEthernetStatus(*curBoardFw); - } - else if (fpgaVer == 3) { - CheckEthernetV3(*curBoardFw, 1); - CheckEthernetV3(*curBoardFw, 2); - } + std::cout << "FPGA Rev " << fpga_ver << std::endl; + if (fpga_ver == 2) { + // Check that KSZ8851 registers are as expected + if (!CheckEthernetV2(*curBoard)) + PrintEthernetStatus(*curBoard); + } + else if (fpga_ver == 3) { + CheckEthernetV3(*curBoard, 1); + CheckEthernetV3(*curBoard, 2); } break; }