$search
00001 /********************************************************************* 00002 * Software License Agreement (BSD License) 00003 * 00004 * Copyright (c) 2008, Willow Garage, Inc. 00005 * All rights reserved. 00006 * 00007 * Redistribution and use in source and binary forms, with or without 00008 * modification, are permitted provided that the following conditions 00009 * are met: 00010 * 00011 * * Redistributions of source code must retain the above copyright 00012 * notice, this list of conditions and the following disclaimer. 00013 * * Redistributions in binary form must reproduce the above 00014 * copyright notice, this list of conditions and the following 00015 * disclaimer in the documentation and/or other materials provided 00016 * with the distribution. 00017 * * Neither the name of the Willow Garage nor the names of its 00018 * contributors may be used to endorse or promote products derived 00019 * from this software without specific prior written permission. 00020 * 00021 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00022 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00023 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00024 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00025 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00026 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00027 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00028 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00029 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00030 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00031 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00032 * POSSIBILITY OF SUCH DAMAGE. 00033 *********************************************************************/ 00034 00035 #include <iomanip> 00036 00037 #include <math.h> 00038 #include <stddef.h> 00039 00040 #include <ethercat_hardware/wg0x.h> 00041 00042 #include <dll/ethercat_dll.h> 00043 #include <al/ethercat_AL.h> 00044 #include <dll/ethercat_device_addressed_telegram.h> 00045 #include <dll/ethercat_frame.h> 00046 00047 #include <boost/crc.hpp> 00048 #include <boost/static_assert.hpp> 00049 #include <boost/make_shared.hpp> 00050 00051 // Temporary,, need 'log' fuction that can switch between fprintf and ROS_LOG. 00052 #define ERR_MODE "\033[41m" 00053 #define STD_MODE "\033[0m" 00054 #define WARN_MODE "\033[43m" 00055 #define GOOD_MODE "\033[42m" 00056 #define INFO_MODE "\033[44m" 00057 00058 #define ERROR_HDR "\033[41mERROR\033[0m" 00059 #define WARN_HDR "\033[43mERROR\033[0m" 00060 00061 00062 unsigned int WG0X::rotateRight8(unsigned in) 00063 { 00064 in &= 0xff; 00065 in = (in >> 1) | (in << 7); 00066 in &= 0xff; 00067 return in; 00068 } 00069 00070 unsigned WG0X::computeChecksum(void const *data, unsigned length) 00071 { 00072 const unsigned char *d = (const unsigned char *)data; 00073 unsigned int checksum = 0x42; 00074 for (unsigned int i = 0; i < length; ++i) 00075 { 00076 checksum = rotateRight8(checksum); 00077 checksum ^= d[i]; 00078 checksum &= 0xff; 00079 } 00080 return checksum; 00081 } 00082 00083 bool WG0XMbxHdr::build(unsigned address, unsigned length, MbxCmdType type, unsigned seqnum) 00084 { 00085 if (type==LOCAL_BUS_WRITE) 00086 { 00087 if (length > MBX_DATA_SIZE) 00088 { 00089 fprintf(stderr, "size of %d is too large for write\n", length); 00090 return false; 00091 } 00092 } 00093 else if (type==LOCAL_BUS_READ) 00094 { 00095 // Result of mailbox read, only stores result data + 1byte checksum 00096 if (length > (MBX_SIZE-1)) 00097 { 00098 fprintf(stderr, "size of %d is too large for read\n", length); 00099 return false; 00100 } 00101 } 00102 else { 00103 assert(0 && "invalid MbxCmdType"); 00104 return false; 00105 } 00106 00107 address_ = address; 00108 length_ = length - 1; 00109 seqnum_ = seqnum; 00110 write_nread_ = (type==LOCAL_BUS_WRITE) ? 1 : 0; 00111 checksum_ = WG0X::rotateRight8(WG0X::computeChecksum(this, sizeof(*this) - 1)); 00112 return true; 00113 } 00114 00115 bool WG0XMbxHdr::verifyChecksum(void) const 00116 { 00117 return WG0X::computeChecksum(this, sizeof(*this)) != 0; 00118 } 00119 00120 bool WG0XMbxCmd::build(unsigned address, unsigned length, MbxCmdType type, unsigned seqnum, void const* data) 00121 { 00122 if (!this->hdr_.build(address, length, type, seqnum)) 00123 { 00124 return false; 00125 } 00126 00127 if (data != NULL) 00128 { 00129 memcpy(data_, data, length); 00130 } 00131 else 00132 { 00133 memset(data_, 0, length); 00134 } 00135 unsigned int checksum = WG0X::rotateRight8(WG0X::computeChecksum(data_, length)); 00136 data_[length] = checksum; 00137 return true; 00138 } 00139 00140 00141 MbxDiagnostics::MbxDiagnostics() : 00142 write_errors_(0), 00143 read_errors_(0), 00144 lock_errors_(0), 00145 retries_(0), 00146 retry_errors_(0) 00147 { 00148 // Empty 00149 } 00150 00151 WG0XDiagnostics::WG0XDiagnostics() : 00152 first_(true), 00153 valid_(false), 00154 safety_disable_total_(0), 00155 undervoltage_total_(0), 00156 over_current_total_(0), 00157 board_over_temp_total_(0), 00158 bridge_over_temp_total_(0), 00159 operate_disable_total_(0), 00160 watchdog_disable_total_(0), 00161 lock_errors_(0), 00162 checksum_errors_(0), 00163 zero_offset_(0), 00164 cached_zero_offset_(0) 00165 { 00166 memset(&safety_disable_status_, 0, sizeof(safety_disable_status_)); 00167 memset(&diagnostics_info_, 0, sizeof(diagnostics_info_)); 00168 } 00169 00176 void WG0XDiagnostics::update(const WG0XSafetyDisableStatus &new_status, const WG0XDiagnosticsInfo &new_diagnostics_info) 00177 { 00178 first_ = false; 00179 safety_disable_total_ += 0xFF & ((uint32_t)(new_status.safety_disable_count_ - safety_disable_status_.safety_disable_count_)); 00180 { 00181 const WG0XSafetyDisableCounters &new_counters(new_diagnostics_info.safety_disable_counters_); 00182 const WG0XSafetyDisableCounters &old_counters(diagnostics_info_.safety_disable_counters_); 00183 undervoltage_total_ += 0xFF & ((uint32_t)(new_counters.undervoltage_count_ - old_counters.undervoltage_count_)); 00184 over_current_total_ += 0xFF & ((uint32_t)(new_counters.over_current_count_ - old_counters.over_current_count_)); 00185 board_over_temp_total_ += 0xFF & ((uint32_t)(new_counters.board_over_temp_count_ - old_counters.board_over_temp_count_)); 00186 bridge_over_temp_total_ += 0xFF & ((uint32_t)(new_counters.bridge_over_temp_count_ - old_counters.bridge_over_temp_count_)); 00187 operate_disable_total_ += 0xFF & ((uint32_t)(new_counters.operate_disable_count_ - old_counters.operate_disable_count_)); 00188 watchdog_disable_total_ += 0xFF & ((uint32_t)(new_counters.watchdog_disable_count_ - old_counters.watchdog_disable_count_)); 00189 } 00190 00191 safety_disable_status_ = new_status; 00192 diagnostics_info_ = new_diagnostics_info; 00193 } 00194 00195 00217 bool WG0XActuatorInfo::verifyCRC() const 00218 { 00219 // Actuator info contains two 00220 BOOST_STATIC_ASSERT(sizeof(WG0XActuatorInfo) == 264); 00221 BOOST_STATIC_ASSERT( offsetof(WG0XActuatorInfo, crc32_256_) == (256-4)); 00222 BOOST_STATIC_ASSERT( offsetof(WG0XActuatorInfo, crc32_264_) == (264-4)); 00223 boost::crc_32_type crc32_256, crc32_264; 00224 crc32_256.process_bytes(this, offsetof(WG0XActuatorInfo, crc32_256_)); 00225 crc32_264.process_bytes(this, offsetof(WG0XActuatorInfo, crc32_264_)); 00226 return ((this->crc32_264_ == crc32_264.checksum()) || (this->crc32_256_ == crc32_256.checksum())); 00227 } 00228 00232 void WG0XActuatorInfo::generateCRC(void) 00233 { 00234 boost::crc_32_type crc32; 00235 crc32.process_bytes(this, offsetof(WG0XActuatorInfo, crc32_256_)); 00236 this->crc32_256_ = crc32.checksum(); 00237 crc32.reset(); 00238 crc32.process_bytes(this, offsetof(WG0XActuatorInfo, crc32_264_)); 00239 this->crc32_264_ = crc32.checksum(); 00240 } 00241 00242 00243 WG0X::WG0X() : 00244 max_current_(0.0), 00245 too_many_dropped_packets_(false), 00246 status_checksum_error_(false), 00247 timestamp_jump_detected_(false), 00248 fpga_internal_reset_detected_(false), 00249 cached_zero_offset_(0), 00250 calibration_status_(NO_CALIBRATION), 00251 last_num_encoder_errors_(0), 00252 app_ram_status_(APP_RAM_MISSING), 00253 motor_model_(NULL), 00254 disable_motor_model_checking_(false) 00255 { 00256 00257 last_timestamp_ = 0; 00258 last_last_timestamp_ = 0; 00259 drops_ = 0; 00260 consecutive_drops_ = 0; 00261 max_consecutive_drops_ = 0; 00262 max_board_temperature_ = 0; 00263 max_bridge_temperature_ = 0; 00264 in_lockout_ = false; 00265 resetting_ = false; 00266 has_error_ = false; 00267 00268 int error; 00269 if ((error = pthread_mutex_init(&wg0x_diagnostics_lock_, NULL)) != 0) 00270 { 00271 ROS_ERROR("WG0X : init diagnostics mutex :%s", strerror(error)); 00272 } 00273 if ((error = pthread_mutex_init(&mailbox_lock_, NULL)) != 0) 00274 { 00275 ROS_ERROR("WG0X : init mailbox mutex :%s", strerror(error)); 00276 } 00277 } 00278 00279 WG0X::~WG0X() 00280 { 00281 delete sh_->get_fmmu_config(); 00282 delete sh_->get_pd_config(); 00283 delete motor_model_; 00284 } 00285 00286 00287 void WG0X::construct(EtherCAT_SlaveHandler *sh, int &start_address) 00288 { 00289 EthercatDevice::construct(sh, start_address); 00290 00291 // WG EtherCAT devices (WG05,WG06,WG21) revisioning scheme 00292 fw_major_ = (sh->get_revision() >> 8) & 0xff; 00293 fw_minor_ = sh->get_revision() & 0xff; 00294 board_major_ = ((sh->get_revision() >> 24) & 0xff) - 1; 00295 board_minor_ = (sh->get_revision() >> 16) & 0xff; 00296 00297 // Would normally configure EtherCAT initialize EtherCAT communication settings here. 00298 // However, since all WG devices are slightly different doesn't make sense to do it here. 00299 // Instead make sub-classes handle this. 00300 } 00301 00302 00309 void WG0X::copyActuatorInfo(ethercat_hardware::ActuatorInfo &out, const WG0XActuatorInfo &in) 00310 { 00311 out.id = in.id_; 00312 out.name = std::string(in.name_); 00313 out.robot_name = in.robot_name_; 00314 out.motor_make = in.motor_make_; 00315 out.motor_model = in.motor_model_; 00316 out.max_current = in.max_current_; 00317 out.speed_constant = in.speed_constant_; 00318 out.motor_resistance = in.resistance_; 00319 out.motor_torque_constant = in.motor_torque_constant_; 00320 out.encoder_reduction = in.encoder_reduction_; 00321 out.pulses_per_revolution = in.pulses_per_revolution_; 00322 } 00323 00324 00327 bool WG0X::initializeMotorModel(pr2_hardware_interface::HardwareInterface *hw, 00328 const string &device_description, 00329 double max_pwm_ratio, 00330 double board_resistance, 00331 bool poor_measured_motor_voltage) 00332 { 00333 if (!hw) 00334 return true; 00335 00336 motor_model_ = new MotorModel(1000); 00337 if (motor_model_ == NULL) 00338 return false; 00339 00340 const ethercat_hardware::ActuatorInfo &ai(actuator_info_msg_); 00341 00342 unsigned product_code = sh_->get_product_code(); 00343 ethercat_hardware::BoardInfo bi; 00344 bi.description = device_description; 00345 bi.product_code = product_code; 00346 bi.pcb = board_major_; 00347 bi.pca = board_minor_; 00348 bi.serial = sh_->get_serial(); 00349 bi.firmware_major = fw_major_; 00350 bi.firmware_minor = fw_minor_; 00351 bi.board_resistance = board_resistance; 00352 bi.max_pwm_ratio = max_pwm_ratio; 00353 bi.hw_max_current = config_info_.absolute_current_limit_ * config_info_.nominal_current_scale_; 00354 bi.poor_measured_motor_voltage = poor_measured_motor_voltage; 00355 00356 if (!motor_model_->initialize(ai,bi)) 00357 return false; 00358 00359 // Create digital out that can be used to force trigger of motor trace 00360 publish_motor_trace_.name_ = string(actuator_info_.name_) + "_publish_motor_trace"; 00361 publish_motor_trace_.command_.data_ = 0; 00362 publish_motor_trace_.state_.data_ = 0; 00363 if (!hw->addDigitalOut(&publish_motor_trace_)) { 00364 ROS_FATAL("A digital out of the name '%s' already exists", publish_motor_trace_.name_.c_str()); 00365 return false; 00366 } 00367 00368 // When working with experimental setups we don't want motor model to halt motors when it detects a problem. 00369 // Allow rosparam to disable motor model halting for a specific motor. 00370 if (!ros::NodeHandle().getParam(ai.name + "/disable_motor_model_checking", disable_motor_model_checking_)) 00371 { 00372 disable_motor_model_checking_ = false; 00373 } 00374 00375 return true; 00376 } 00377 00378 00379 boost::shared_ptr<ethercat_hardware::MotorHeatingModelCommon> WG0X::motor_heating_model_common_; 00380 00381 bool WG0X::initializeMotorHeatingModel(bool allow_unprogrammed) 00382 { 00383 00384 EthercatDirectCom com(EtherCAT_DataLinkLayer::instance()); 00385 ethercat_hardware::MotorHeatingModelParametersEepromConfig config; 00386 if (!readMotorHeatingModelParametersFromEeprom(&com, config)) 00387 { 00388 ROS_FATAL("Unable to read motor heating model config parameters from EEPROM"); 00389 return false; 00390 } 00391 00392 // All devices need to have motor model heating model parameters stored in them... 00393 // Even if device doesn't use paramers, they should be there. 00394 if (!config.verifyCRC()) 00395 { 00396 if (allow_unprogrammed) 00397 { 00398 ROS_WARN("%s EEPROM does not contain motor heating model parameters", 00399 actuator_info_.name_); 00400 return true; 00401 } 00402 else 00403 { 00404 ROS_WARN("%s EEPROM does not contain motor heating model parameters", 00405 actuator_info_.name_); 00406 return true; 00407 // TODO: once there is ability to update all MCB iwth motorconf, this is will become a fatal error 00408 ROS_FATAL("%s EEPROM does not contain motor heating model parameters", 00409 actuator_info_.name_); 00410 return false; 00411 } 00412 } 00413 00414 // Even though all devices should contain motor heating model parameters, 00415 // The heating model does not need to be used. 00416 if (config.enforce_ == 0) 00417 { 00418 return true; 00419 } 00420 00421 // Don't need motor model if we are not using ROS (motorconf) 00422 if (!use_ros_) 00423 { 00424 return true; 00425 } 00426 00427 // Generate hwid for motor model 00428 std::ostringstream hwid; 00429 hwid << unsigned(sh_->get_product_code()) << std::setw(5) << std::setfill('0') << unsigned(sh_->get_serial()); 00430 00431 // All motor heating models use shared settings structure 00432 if (motor_heating_model_common_.get() == NULL) 00433 { 00434 ros::NodeHandle nh("~motor_heating_model"); 00435 motor_heating_model_common_ = boost::make_shared<ethercat_hardware::MotorHeatingModelCommon>(nh); 00436 motor_heating_model_common_->initialize(); 00437 // Only display following warnings once. 00438 if (!motor_heating_model_common_->enable_model_) 00439 { 00440 ROS_WARN("Motor heating model disabled for all devices"); 00441 } 00442 if (!motor_heating_model_common_->load_save_files_) 00443 { 00444 ROS_WARN("Not loading motor heating model files"); 00445 } 00446 if (!motor_heating_model_common_->update_save_files_) 00447 { 00448 ROS_WARN("Not saving motor heating model files"); 00449 } 00450 } 00451 00452 if (!motor_heating_model_common_->enable_model_) 00453 { 00454 return true; 00455 } 00456 00457 motor_heating_model_ = 00458 boost::make_shared<ethercat_hardware::MotorHeatingModel>(config.params_, 00459 actuator_info_.name_, 00460 hwid.str(), 00461 motor_heating_model_common_->save_directory_); 00462 // have motor heating model load last saved temperaures from filesystem 00463 if (motor_heating_model_common_->load_save_files_) 00464 { 00465 if (!motor_heating_model_->loadTemperatureState()) 00466 { 00467 ROS_WARN("Could not load motor temperature state for %s", actuator_info_.name_); 00468 } 00469 } 00470 if (motor_heating_model_common_->publish_temperature_) 00471 { 00472 motor_heating_model_->startTemperaturePublisher(); 00473 } 00474 motor_heating_model_common_->attach(motor_heating_model_); 00475 00476 return true; 00477 } 00478 00479 00480 int WG0X::initialize(pr2_hardware_interface::HardwareInterface *hw, bool allow_unprogrammed) 00481 { 00482 ROS_DEBUG("Device #%02d: WG0%d (%#08x) Firmware Revision %d.%02d, PCB Revision %c.%02d, Serial #: %d", 00483 sh_->get_ring_position(), 00484 sh_->get_product_code() % 100, 00485 sh_->get_product_code(), fw_major_, fw_minor_, 00486 'A' + board_major_, board_minor_, 00487 sh_->get_serial()); 00488 00489 EthercatDirectCom com(EtherCAT_DataLinkLayer::instance()); 00490 00491 if (sh_->get_product_code() == WG05_PRODUCT_CODE) 00492 { 00493 if (fw_major_ != 1 || fw_minor_ < 7) 00494 { 00495 ROS_FATAL("Unsupported firmware revision %d.%02d", fw_major_, fw_minor_); 00496 return -1; 00497 } 00498 } 00499 else 00500 { 00501 if ((fw_major_ == 0 && fw_minor_ < 4) /*|| (fw_major_ == 1 && fw_minor_ < 0)*/) 00502 { 00503 ROS_FATAL("Unsupported firmware revision %d.%02d", fw_major_, fw_minor_); 00504 return -1; 00505 } 00506 } 00507 00508 if (readMailbox(&com, WG0XConfigInfo::CONFIG_INFO_BASE_ADDR, &config_info_, sizeof(config_info_)) != 0) 00509 { 00510 ROS_FATAL("Unable to load configuration information"); 00511 return -1; 00512 } 00513 ROS_DEBUG(" Serial #: %05d", config_info_.device_serial_number_); 00514 double board_max_current = double(config_info_.absolute_current_limit_) * config_info_.nominal_current_scale_; 00515 00516 if (!readActuatorInfoFromEeprom(&com, actuator_info_)) 00517 { 00518 ROS_FATAL("Unable to read actuator info from EEPROM"); 00519 return -1; 00520 } 00521 00522 if (actuator_info_.verifyCRC()) 00523 { 00524 if (actuator_info_.major_ != 0 || actuator_info_.minor_ != 2) 00525 { 00526 if (allow_unprogrammed) 00527 ROS_WARN("Unsupported actuator info version (%d.%d != 0.2). Please reprogram device #%02d", actuator_info_.major_, actuator_info_.minor_, sh_->get_ring_position()); 00528 else 00529 { 00530 ROS_FATAL("Unsupported actuator info version (%d.%d != 0.2). Please reprogram device #%02d", actuator_info_.major_, actuator_info_.minor_, sh_->get_ring_position()); 00531 return -1; 00532 } 00533 } 00534 00535 actuator_.name_ = actuator_info_.name_; 00536 ROS_DEBUG(" Name: %s", actuator_info_.name_); 00537 00538 // Copy actuator info read from eeprom, into msg type 00539 copyActuatorInfo(actuator_info_msg_, actuator_info_); 00540 00541 if (!initializeMotorHeatingModel(allow_unprogrammed)) 00542 { 00543 return -1; 00544 } 00545 00546 00547 bool isWG021 = sh_->get_product_code() == WG021_PRODUCT_CODE; 00548 if (!isWG021) 00549 { 00550 // Register actuator with pr2_hardware_interface::HardwareInterface 00551 if (hw && !hw->addActuator(&actuator_)) 00552 { 00553 ROS_FATAL("An actuator of the name '%s' already exists. Device #%02d has a duplicate name", actuator_.name_.c_str(), sh_->get_ring_position()); 00554 return -1; 00555 } 00556 00557 } 00558 00559 // Register digital out with pr2_hardware_interface::HardwareInterface 00560 digital_out_.name_ = actuator_info_.name_; 00561 if (hw && !hw->addDigitalOut(&digital_out_)) 00562 { 00563 ROS_FATAL("A digital out of the name '%s' already exists. Device #%02d has a duplicate name", digital_out_.name_.c_str(), sh_->get_ring_position()); 00564 return -1; 00565 } 00566 00567 // If it is supported, read application ram data. 00568 if (app_ram_status_ == APP_RAM_PRESENT) 00569 { 00570 double zero_offset; 00571 if (readAppRam(&com, zero_offset)) 00572 { 00573 ROS_DEBUG("Read calibration from device %s: %f", actuator_info_.name_, zero_offset); 00574 actuator_.state_.zero_offset_ = zero_offset; 00575 cached_zero_offset_ = zero_offset; 00576 calibration_status_ = SAVED_CALIBRATION; 00577 } 00578 else 00579 { 00580 ROS_DEBUG("No calibration offset was stored on device %s", actuator_info_.name_); 00581 } 00582 } 00583 else if (app_ram_status_ == APP_RAM_MISSING) 00584 { 00585 ROS_WARN("Device %s does not support storing calibration offsets", actuator_info_.name_); 00586 } 00587 else if (app_ram_status_ == APP_RAM_NOT_APPLICABLE) 00588 { 00589 // don't produce warning 00590 } 00591 00592 // Make sure motor current limit is less than board current limit 00593 if (actuator_info_.max_current_ > board_max_current) 00594 { 00595 ROS_WARN("WARNING: Device #%02d : motor current limit (%f) greater than board current limit (%f)", 00596 sh_->get_ring_position(), actuator_info_.max_current_, board_max_current); 00597 } 00598 max_current_ = std::min(board_max_current, actuator_info_.max_current_); 00599 } 00600 else if (allow_unprogrammed) 00601 { 00602 ROS_WARN("WARNING: Device #%02d (%d%05d) is not programmed", 00603 sh_->get_ring_position(), sh_->get_product_code(), sh_->get_serial()); 00604 //actuator_info_.crc32_264_ = 0; 00605 //actuator_info_.crc32_256_ = 0; 00606 00607 max_current_ = board_max_current; 00608 } 00609 else 00610 { 00611 ROS_FATAL("Device #%02d (%d%05d) is not programmed, aborting...", 00612 sh_->get_ring_position(), sh_->get_product_code(), sh_->get_serial()); 00613 return -1; 00614 } 00615 00616 return 0; 00617 } 00618 00619 #define GET_ATTR(a) \ 00620 { \ 00621 TiXmlElement *c; \ 00622 attr = elt->Attribute((a)); \ 00623 if (!attr) { \ 00624 c = elt->FirstChildElement((a)); \ 00625 if (!c || !(attr = c->GetText())) { \ 00626 ROS_FATAL("Actuator is missing the attribute "#a); \ 00627 exit(EXIT_FAILURE); \ 00628 } \ 00629 } \ 00630 } 00631 00632 void WG0X::clearErrorFlags(void) 00633 { 00634 has_error_ = false; 00635 too_many_dropped_packets_ = false; 00636 status_checksum_error_ = false; 00637 timestamp_jump_detected_ = false; 00638 if (motor_model_) motor_model_->reset(); 00639 if (motor_heating_model_.get() != NULL) 00640 { 00641 motor_heating_model_->reset(); 00642 } 00643 } 00644 00645 void WG0X::packCommand(unsigned char *buffer, bool halt, bool reset) 00646 { 00647 pr2_hardware_interface::ActuatorCommand &cmd = actuator_.command_; 00648 00649 if (halt) 00650 { 00651 cmd.effort_ = 0; 00652 } 00653 00654 if (reset) 00655 { 00656 clearErrorFlags(); 00657 } 00658 resetting_ = reset; 00659 00660 // If zero_offset was changed, give it to non-realtime thread 00661 double zero_offset = actuator_.state_.zero_offset_; 00662 if (zero_offset != cached_zero_offset_) 00663 { 00664 if (tryLockWG0XDiagnostics()) 00665 { 00666 ROS_DEBUG("Calibration change of %s, new %f, old %f", actuator_info_.name_, zero_offset, cached_zero_offset_); 00667 cached_zero_offset_ = zero_offset; 00668 wg0x_collect_diagnostics_.zero_offset_ = zero_offset; 00669 calibration_status_ = CONTROLLER_CALIBRATION; 00670 unlockWG0XDiagnostics(); 00671 } 00672 else 00673 { 00674 // It is OK if trylock failed, this will still try again next cycle. 00675 } 00676 } 00677 00678 // Compute the current 00679 double current = (cmd.effort_ / actuator_info_.encoder_reduction_) / actuator_info_.motor_torque_constant_ ; 00680 actuator_.state_.last_commanded_effort_ = cmd.effort_; 00681 actuator_.state_.last_commanded_current_ = current; 00682 00683 // Truncate the current to limit 00684 current = max(min(current, max_current_), -max_current_); 00685 00686 // Pack command structures into EtherCAT buffer 00687 WG0XCommand *c = (WG0XCommand *)buffer; 00688 memset(c, 0, command_size_); 00689 c->programmed_current_ = int(current / config_info_.nominal_current_scale_); 00690 c->mode_ = (cmd.enable_ && !halt && !has_error_) ? (MODE_ENABLE | MODE_CURRENT) : MODE_OFF; 00691 c->mode_ |= (reset ? MODE_SAFETY_RESET : 0); 00692 c->digital_out_ = digital_out_.command_.data_; 00693 c->checksum_ = rotateRight8(computeChecksum(c, command_size_ - 1)); 00694 } 00695 00696 bool WG0X::unpackState(unsigned char *this_buffer, unsigned char *prev_buffer) 00697 { 00698 pr2_hardware_interface::ActuatorState &state = actuator_.state_; 00699 WG0XStatus *this_status, *prev_status; 00700 00701 this_status = (WG0XStatus *)(this_buffer + command_size_); 00702 prev_status = (WG0XStatus *)(prev_buffer + command_size_); 00703 00704 digital_out_.state_.data_ = this_status->digital_out_; 00705 00706 // Do not report timestamp directly to controllers because 32bit integer 00707 // value in microseconds will overflow every 72 minutes. 00708 // Instead a accumulate small time differences into a ros::Duration variable 00709 int32_t timediff = WG0X::timestampDiff(this_status->timestamp_, prev_status->timestamp_); 00710 sample_timestamp_ += WG0X::timediffToDuration(timediff); 00711 state.sample_timestamp_ = sample_timestamp_; //ros::Duration is preferred source of time for controllers 00712 state.timestamp_ = sample_timestamp_.toSec(); //double value is for backwards compatibility 00713 00714 state.device_id_ = sh_->get_ring_position(); 00715 00716 state.encoder_count_ = this_status->encoder_count_; 00717 state.position_ = double(this_status->encoder_count_) / actuator_info_.pulses_per_revolution_ * 2 * M_PI - state.zero_offset_; 00718 00719 state.encoder_velocity_ = 00720 calcEncoderVelocity(this_status->encoder_count_, this_status->timestamp_, 00721 prev_status->encoder_count_, prev_status->timestamp_); 00722 state.velocity_ = state.encoder_velocity_ / actuator_info_.pulses_per_revolution_ * 2 * M_PI; 00723 00724 state.calibration_reading_ = this_status->calibration_reading_ & LIMIT_SENSOR_0_STATE; 00725 state.calibration_rising_edge_valid_ = this_status->calibration_reading_ & LIMIT_OFF_TO_ON; 00726 state.calibration_falling_edge_valid_ = this_status->calibration_reading_ & LIMIT_ON_TO_OFF; 00727 state.last_calibration_rising_edge_ = double(this_status->last_calibration_rising_edge_) / actuator_info_.pulses_per_revolution_ * 2 * M_PI; 00728 state.last_calibration_falling_edge_ = double(this_status->last_calibration_falling_edge_) / actuator_info_.pulses_per_revolution_ * 2 * M_PI; 00729 state.is_enabled_ = bool(this_status->mode_ & MODE_ENABLE); 00730 00731 state.last_executed_current_ = this_status->programmed_current_ * config_info_.nominal_current_scale_; 00732 state.last_measured_current_ = this_status->measured_current_ * config_info_.nominal_current_scale_; 00733 00734 state.last_executed_effort_ = this_status->programmed_current_ * config_info_.nominal_current_scale_ * actuator_info_.motor_torque_constant_ * actuator_info_.encoder_reduction_; 00735 state.last_measured_effort_ = this_status->measured_current_ * config_info_.nominal_current_scale_ * actuator_info_.motor_torque_constant_ * actuator_info_.encoder_reduction_; 00736 00737 state.num_encoder_errors_ = this_status->num_encoder_errors_; 00738 00739 state.motor_voltage_ = this_status->motor_voltage_ * config_info_.nominal_voltage_scale_; 00740 00741 state.max_effort_ = max_current_ * actuator_info_.encoder_reduction_ * actuator_info_.motor_torque_constant_; 00742 00743 return verifyState(this_status, prev_status); 00744 } 00745 00746 00747 bool WG0X::verifyChecksum(const void* buffer, unsigned size) 00748 { 00749 bool success = computeChecksum(buffer, size) == 0; 00750 if (!success) { 00751 if (tryLockWG0XDiagnostics()) { 00752 ++wg0x_collect_diagnostics_.checksum_errors_; 00753 unlockWG0XDiagnostics(); 00754 } 00755 } 00756 return success; 00757 } 00758 00759 00760 00767 int32_t WG0X::timestampDiff(uint32_t new_timestamp, uint32_t old_timestamp) 00768 { 00769 return int32_t(new_timestamp - old_timestamp); 00770 } 00771 00778 ros::Duration WG0X::timediffToDuration(int32_t timediff_usec) 00779 { 00780 static const int USEC_PER_SEC = 1000000; 00781 int sec = timediff_usec / USEC_PER_SEC; 00782 int nsec = (timediff_usec % USEC_PER_SEC)*1000; 00783 return ros::Duration(sec,nsec); 00784 } 00785 00786 00792 int32_t WG0X::positionDiff(int32_t new_position, int32_t old_position) 00793 { 00794 return int32_t(new_position - old_position); 00795 } 00796 00803 double WG0X::calcEncoderVelocity(int32_t new_position, uint32_t new_timestamp, 00804 int32_t old_position, uint32_t old_timestamp) 00805 { 00806 double timestamp_diff = double(timestampDiff(new_timestamp, old_timestamp)) * 1e-6; 00807 double position_diff = double(positionDiff(new_position, old_position)); 00808 return (position_diff / timestamp_diff); 00809 } 00810 00811 00818 double WG0X::convertRawTemperature(int16_t raw_temp) 00819 { 00820 return 0.0078125 * double(raw_temp); 00821 } 00822 00823 00824 // Returns true if timestamp changed by more than (amount) or time goes in reverse. 00825 bool WG0X::timestamp_jump(uint32_t timestamp, uint32_t last_timestamp, uint32_t amount) 00826 { 00827 uint32_t timestamp_diff = (timestamp - last_timestamp); 00828 return (timestamp_diff > amount); 00829 } 00830 00831 bool WG0X::verifyState(WG0XStatus *this_status, WG0XStatus *prev_status) 00832 { 00833 pr2_hardware_interface::ActuatorState &state = actuator_.state_; 00834 bool rv = true; 00835 00836 if ((motor_model_ != NULL) || (motor_heating_model_ != NULL)) 00837 { 00838 // Both motor model and motor heating model use MotorTraceSample 00839 ethercat_hardware::MotorTraceSample &s(motor_trace_sample_); 00840 double last_executed_current = this_status->programmed_current_ * config_info_.nominal_current_scale_; 00841 double supply_voltage = double(prev_status->supply_voltage_) * config_info_.nominal_voltage_scale_; 00842 double pwm_ratio = double(this_status->programmed_pwm_value_) / double(PWM_MAX); 00843 s.timestamp = state.timestamp_; 00844 s.enabled = state.is_enabled_; 00845 s.supply_voltage = supply_voltage; 00846 s.measured_motor_voltage = state.motor_voltage_; 00847 s.programmed_pwm = pwm_ratio; 00848 s.executed_current = last_executed_current; 00849 s.measured_current = state.last_measured_current_; 00850 s.velocity = state.velocity_; 00851 s.encoder_position = state.position_; 00852 s.encoder_error_count = state.num_encoder_errors_; 00853 00854 if (motor_model_ != NULL) 00855 { 00856 // Collect data for motor model 00857 motor_model_->sample(s); 00858 motor_model_->checkPublish(); 00859 } 00860 if (motor_heating_model_ != NULL) 00861 { 00862 double ambient_temperature = convertRawTemperature(this_status->board_temperature_); 00863 double duration = double(timestampDiff(this_status->timestamp_, prev_status->timestamp_)) * 1e-6; 00864 motor_heating_model_->update(s, actuator_info_msg_, ambient_temperature, duration); 00865 00866 if ((!motor_heating_model_common_->disable_halt_) && (motor_heating_model_->hasOverheated())) 00867 { 00868 rv = false; 00869 } 00870 } 00871 } 00872 00873 max_board_temperature_ = max(max_board_temperature_, this_status->board_temperature_); 00874 max_bridge_temperature_ = max(max_bridge_temperature_, this_status->bridge_temperature_); 00875 00876 if (this_status->timestamp_ == last_timestamp_ || 00877 this_status->timestamp_ == last_last_timestamp_) { 00878 ++drops_; 00879 ++consecutive_drops_; 00880 max_consecutive_drops_ = max(max_consecutive_drops_, consecutive_drops_); 00881 } else { 00882 consecutive_drops_ = 0; 00883 } 00884 // Detect timestamps going in reverse or changing by more than 10 seconds = 10,000,000 usec 00885 if ( timestamp_jump(this_status->timestamp_,last_timestamp_,10000000) ) 00886 { 00887 timestamp_jump_detected_ = true; 00888 } 00889 last_last_timestamp_ = last_timestamp_; 00890 last_timestamp_ = this_status->timestamp_; 00891 00892 if (consecutive_drops_ > 10) 00893 { 00894 too_many_dropped_packets_ = true; 00895 rv = false; 00896 goto end; 00897 } 00898 00899 in_lockout_ = bool(this_status->mode_ & MODE_SAFETY_LOCKOUT); 00900 if (in_lockout_ && !resetting_) 00901 { 00902 rv = false; 00903 goto end; 00904 } 00905 00906 if (fpga_internal_reset_detected_) 00907 { 00908 rv = false; 00909 goto end; 00910 } 00911 00912 if (state.is_enabled_ && motor_model_) 00913 { 00914 if (!disable_motor_model_checking_) 00915 { 00916 if(!motor_model_->verify()) 00917 { 00918 // Motor model will automatically publish a motor trace when there is an error 00919 rv = false; 00920 goto end; 00921 } 00922 } 00923 } 00924 00925 end: 00926 if (motor_model_) 00927 { 00928 // Publish trace when: 00929 // * device goes into safety lockout 00930 // * controller request motor trace to be published 00931 bool new_error = in_lockout_ && !resetting_ && !has_error_; 00932 if (new_error || publish_motor_trace_.command_.data_) 00933 { 00934 const char* reason = (new_error) ? "Safety Lockout" : "Publishing manually triggered"; 00935 int level = (new_error) ? 2 : 0; 00936 motor_model_->flagPublish(reason, level , 100); 00937 publish_motor_trace_.command_.data_ = 0; 00938 } 00939 } 00940 bool is_error = !rv; 00941 has_error_ = is_error || has_error_; 00942 actuator_.state_.halted_ = has_error_ || this_status->mode_ == MODE_OFF; 00943 return rv; 00944 } 00945 00946 bool WG0X::publishTrace(const string &reason, unsigned level, unsigned delay) 00947 { 00948 if (motor_model_) 00949 { 00950 motor_model_->flagPublish(reason, level, delay); 00951 return true; 00952 } 00953 return false; 00954 } 00955 00956 00957 void WG0X::collectDiagnostics(EthercatCom *com) 00958 { 00959 //Collect safety disable information through mailbox 00960 bool success = false; 00961 00962 // Have parent collect diagnositcs 00963 EthercatDevice::collectDiagnostics(com); 00964 00965 // Send a packet with both a Fixed address read (NPRW) to device to make sure it is present in chain. 00966 // This avoids wasting time trying to read mailbox of device that it not present on chain. 00967 { 00968 EC_Logic *logic = EC_Logic::instance(); 00969 unsigned char buf[1]; 00970 EC_UINT address = 0x0000; 00971 NPRD_Telegram nprd_telegram(logic->get_idx(), 00972 sh_->get_station_address(), 00973 address, 00974 0 /*working counter*/, 00975 sizeof(buf), 00976 buf); 00977 EC_Ethernet_Frame frame(&nprd_telegram); 00978 if (!com->txandrx_once(&frame)) { 00979 // packet didn't come back 00980 goto end; 00981 } 00982 if (nprd_telegram.get_wkc() != 1) { 00983 // packet came back, but device didn't not respond 00984 goto end; 00985 } 00986 } 00987 00988 WG0XSafetyDisableStatus s; 00989 if (readMailbox(com, s.BASE_ADDR, &s, sizeof(s)) != 0) { 00990 goto end; 00991 } 00992 00993 WG0XDiagnosticsInfo di; 00994 if (readMailbox(com, di.BASE_ADDR, &di, sizeof(di)) != 0) { 00995 goto end; 00996 } 00997 00998 { // Try writing zero offset to to WG0X devices that have application ram 00999 WG0XDiagnostics &dg(wg0x_collect_diagnostics_); 01000 01001 if ((app_ram_status_ == APP_RAM_PRESENT) && (dg.zero_offset_ != dg.cached_zero_offset_)) 01002 { 01003 if (writeAppRam(com, dg.zero_offset_)){ 01004 ROS_DEBUG("Writing new calibration to device %s, new %f, old %f", actuator_info_.name_, dg.zero_offset_, dg.cached_zero_offset_); 01005 dg.cached_zero_offset_ = dg.zero_offset_; 01006 } 01007 else{ 01008 ROS_ERROR("Failed to write new calibration to device %s, new %f, old %f", actuator_info_.name_, dg.zero_offset_, dg.cached_zero_offset_); 01009 // Diagnostics thread will try again next update cycle 01010 } 01011 } 01012 } 01013 01014 success = true; 01015 01016 end: 01017 if (!lockWG0XDiagnostics()) { 01018 wg0x_collect_diagnostics_.valid_ = false; // change these values even if we did't get the lock 01019 wg0x_collect_diagnostics_.first_ = false; 01020 return; 01021 } 01022 01023 wg0x_collect_diagnostics_.valid_ = success; 01024 if (success) { 01025 wg0x_collect_diagnostics_.update(s,di); 01026 } 01027 01028 unlockWG0XDiagnostics(); 01029 } 01030 01031 01032 bool WG0X::writeAppRam(EthercatCom *com, double zero_offset) 01033 { 01034 WG0XUserConfigRam cfg; 01035 cfg.version_ = 1; 01036 cfg.zero_offset_ = zero_offset; 01037 boost::crc_32_type crc32; 01038 crc32.process_bytes(&cfg, sizeof(cfg)-sizeof(cfg.crc32_)); 01039 cfg.crc32_ = crc32.checksum(); 01040 return (writeMailbox(com, WG0XUserConfigRam::BASE_ADDR, &cfg, sizeof(cfg)) == 0); 01041 } 01042 01043 bool WG0X::readAppRam(EthercatCom *com, double &zero_offset) 01044 { 01045 WG0XUserConfigRam cfg; 01046 if (!readMailbox(com, WG0XUserConfigRam::BASE_ADDR, &cfg, sizeof(cfg)) == 0) 01047 { 01048 return false; 01049 } 01050 if (cfg.version_ != 1) 01051 { 01052 return false; 01053 } 01054 boost::crc_32_type crc32; 01055 crc32.process_bytes(&cfg, sizeof(cfg)-sizeof(cfg.crc32_)); 01056 if (cfg.crc32_ != crc32.checksum()) { 01057 return false; 01058 } 01059 zero_offset = cfg.zero_offset_; 01060 return true; 01061 } 01062 01063 01072 bool WG0X::waitForSpiEepromReady(EthercatCom *com) 01073 { 01074 WG0XSpiEepromCmd cmd; 01075 // TODO : poll for a given number of millseconds instead of a given number of cycles 01076 //start_time = 0; 01077 unsigned tries = 0; 01078 do { 01079 //read_time = time; 01080 ++tries; 01081 if (!readSpiEepromCmd(com, cmd)) 01082 { 01083 ROS_ERROR("Error reading SPI Eeprom Cmd busy bit"); 01084 return false; 01085 } 01086 01087 if (!cmd.busy_) 01088 { 01089 return true; 01090 } 01091 01092 usleep(100); 01093 } while (tries <= 10); 01094 01095 ROS_ERROR("Timed out waiting for SPI state machine to be idle (%d)", tries); 01096 return false; 01097 } 01098 01099 01109 bool WG0X::sendSpiEepromCmd(EthercatCom *com, const WG0XSpiEepromCmd &cmd) 01110 { 01111 if (!waitForSpiEepromReady(com)) 01112 { 01113 return false; 01114 } 01115 01116 // Send command 01117 if (writeMailbox(com, WG0XSpiEepromCmd::SPI_COMMAND_ADDR, &cmd, sizeof(cmd))) 01118 { 01119 ROS_ERROR("Error writing SPI EEPROM command"); 01120 return false; 01121 } 01122 01123 // Now read back SPI EEPROM state machine register, and check : 01124 // 1. for state machine to become ready 01125 // 2. that command data was properly write and not corrupted 01126 WG0XSpiEepromCmd stat; 01127 unsigned tries = 0; 01128 do 01129 { 01130 if (!readSpiEepromCmd(com, stat)) 01131 { 01132 return false; 01133 } 01134 01135 if (stat.operation_ != cmd.operation_) 01136 { 01137 ROS_ERROR("Invalid readback of SPI EEPROM operation : got 0x%X, expected 0x%X\n", stat.operation_, cmd.operation_); 01138 return false; 01139 } 01140 01141 // return true if command has completed 01142 if (!stat.busy_) 01143 { 01144 if (tries > 0) 01145 { 01146 ROS_WARN("Eeprom state machine took %d cycles", tries); 01147 } 01148 return true;; 01149 } 01150 01151 fprintf(stderr, "eeprom busy reading again, waiting...\n"); 01152 usleep(100); 01153 } while (++tries < 10); 01154 01155 ROS_ERROR("Eeprom SPI state machine busy after %d cycles", tries); 01156 return false; 01157 } 01158 01159 01172 bool WG0X::readEepromPage(EthercatCom *com, unsigned page, void* data, unsigned length) 01173 { 01174 if (length > MAX_EEPROM_PAGE_SIZE) 01175 { 01176 ROS_ERROR("Eeprom read length %d > %d", length, MAX_EEPROM_PAGE_SIZE); 01177 return false; 01178 } 01179 01180 if (page >= NUM_EEPROM_PAGES) 01181 { 01182 ROS_ERROR("Eeprom read page %d > %d", page, NUM_EEPROM_PAGES-1); 01183 return false; 01184 } 01185 01186 // Since we don't know the size of the eeprom there is not always 264 bytes available. 01187 // This may try to read 264 bytes, but only the first 256 bytes may be valid. 01188 // To avoid any odd issue, zero out FPGA buffer before asking for eeprom data. 01189 memset(data,0,length); 01190 if (writeMailbox(com, WG0XSpiEepromCmd::SPI_BUFFER_ADDR, &actuator_info_, sizeof(actuator_info_))) 01191 { 01192 ROS_ERROR("Error zeroing eeprom data buffer"); 01193 return false; 01194 } 01195 01196 // Send command to SPI state machine to perform read of eeprom, 01197 // sendSpiEepromCmd will automatically wait for SPI state machine 01198 // to be idle before a new command is sent 01199 WG0XSpiEepromCmd cmd; 01200 memset(&cmd,0,sizeof(cmd)); 01201 cmd.build_read(page); 01202 if (!sendSpiEepromCmd(com, cmd)) 01203 { 01204 ROS_ERROR("Error sending SPI read command"); 01205 return false; 01206 } 01207 01208 // Wait for SPI Eeprom Read to complete 01209 // sendSPICommand will wait for Command to finish before returning 01210 01211 // Read eeprom page data from FPGA buffer 01212 if (readMailbox(com, WG0XSpiEepromCmd::SPI_BUFFER_ADDR, data, length)) 01213 { 01214 ROS_ERROR("Error reading eeprom data from buffer"); 01215 return false; 01216 } 01217 01218 return true; 01219 } 01220 01221 01229 bool WG0X::readActuatorInfoFromEeprom(EthercatCom *com, WG0XActuatorInfo &actuator_info) 01230 { 01231 BOOST_STATIC_ASSERT(sizeof(actuator_info) == 264); 01232 01233 if (!readEepromPage(com, ACTUATOR_INFO_PAGE, &actuator_info, sizeof(actuator_info))) 01234 { 01235 ROS_ERROR("Reading acutuator info from eeprom"); 01236 return false; 01237 } 01238 return true; 01239 } 01240 01248 bool WG0X::readMotorHeatingModelParametersFromEeprom(EthercatCom *com, MotorHeatingModelParametersEepromConfig &config) 01249 { 01250 BOOST_STATIC_ASSERT(sizeof(config) == 256); 01251 01252 if (!readEepromPage(com, config.EEPROM_PAGE, &config, sizeof(config))) 01253 { 01254 ROS_ERROR("Reading motor heating model config from eeprom"); 01255 return false; 01256 } 01257 return true; 01258 } 01259 01260 01261 01275 bool WG0X::writeEepromPage(EthercatCom *com, unsigned page, const void* data, unsigned length) 01276 { 01277 if (length > 264) 01278 { 01279 ROS_ERROR("Eeprom write length %d > %d", length, MAX_EEPROM_PAGE_SIZE); 01280 return false; 01281 } 01282 01283 if (page >= NUM_EEPROM_PAGES) 01284 { 01285 ROS_ERROR("Eeprom write page %d > %d", page, NUM_EEPROM_PAGES-1); 01286 return false; 01287 } 01288 01289 // wait for eeprom to be ready before write data into FPGA buffer 01290 if (!waitForSpiEepromReady(com)) 01291 { 01292 return false; 01293 } 01294 01295 const void *write_buf = data; 01296 01297 // if needed, pad data to 264 byte in buf 01298 uint8_t buf[MAX_EEPROM_PAGE_SIZE]; 01299 if (length < MAX_EEPROM_PAGE_SIZE) 01300 { 01301 memcpy(buf, data, length); 01302 memset(buf+length, 0xFF, MAX_EEPROM_PAGE_SIZE-length); 01303 write_buf = buf; 01304 } 01305 01306 // Write data to FPGA buffer 01307 if (writeMailbox(com, WG0XSpiEepromCmd::SPI_BUFFER_ADDR, write_buf, MAX_EEPROM_PAGE_SIZE)) 01308 { 01309 ROS_ERROR("Write of SPI EEPROM buffer failed"); 01310 return false; 01311 } 01312 01313 // Have SPI EEPROM state machine start SPI data transfer 01314 WG0XSpiEepromCmd cmd; 01315 cmd.build_write(page); 01316 if (!sendSpiEepromCmd(com, cmd)) 01317 { 01318 ROS_ERROR("Error giving SPI EEPROM write command"); 01319 return false; 01320 } 01321 01322 // Wait for EEPROM write to complete 01323 if (!waitForEepromReady(com)) 01324 { 01325 return false; 01326 } 01327 01328 return true; 01329 } 01330 01331 01343 bool WG0X::waitForEepromReady(EthercatCom *com) 01344 { 01345 // Wait for eeprom write to complete 01346 unsigned tries = 0; 01347 EepromStatusReg status_reg; 01348 do { 01349 if (!readEepromStatusReg(com, status_reg)) 01350 { 01351 return false; 01352 } 01353 if (status_reg.ready_) 01354 { 01355 break; 01356 } 01357 usleep(100); 01358 } while (++tries < 20); 01359 01360 if (!status_reg.ready_) 01361 { 01362 ROS_ERROR("Eeprom still busy after %d cycles", tries); 01363 return false; 01364 } 01365 01366 if (tries > 10) 01367 { 01368 ROS_WARN("EEPROM took %d cycles to be ready", tries); 01369 } 01370 return true; 01371 } 01372 01373 01374 01385 bool WG0X::readEepromStatusReg(EthercatCom *com, EepromStatusReg ®) 01386 { 01387 // Status is read from EEPROM by having SPI state machine perform an "abitrary" operation. 01388 // With an arbitrary operation, the SPI state machine shifts out byte from buffer, while 01389 // storing byte shifted in from device into same location in buffer. 01390 // SPI state machine has no idea what command it is sending device or how to intpret its result. 01391 01392 // To read eeprom status register, we transfer 2 bytes. The first byte is the read status register 01393 // command value (0xD7). When transfering the second byte, the EEPROM should send its status. 01394 char data[2] = {0xD7, 0x00}; 01395 BOOST_STATIC_ASSERT(sizeof(data) == 2); 01396 if (writeMailbox(com, WG0XSpiEepromCmd::SPI_BUFFER_ADDR, data, sizeof(data))) 01397 { 01398 ROS_ERROR("Writing SPI buffer"); 01399 return false; 01400 } 01401 01402 { // Have SPI state machine trasfer 2 bytes 01403 WG0XSpiEepromCmd cmd; 01404 cmd.build_arbitrary(sizeof(data)); 01405 if (!sendSpiEepromCmd(com, cmd)) 01406 { 01407 ROS_ERROR("Sending SPI abitrary command"); 01408 return false; 01409 } 01410 } 01411 01412 // Data read from device should now be stored in FPGA buffer 01413 if (readMailbox(com, WG0XSpiEepromCmd::SPI_BUFFER_ADDR, data, sizeof(data))) 01414 { 01415 ROS_ERROR("Reading status register data from SPI buffer"); 01416 return false; 01417 } 01418 01419 // Status register would be second byte of buffer 01420 reg.raw_ = data[1]; 01421 return true; 01422 } 01423 01424 01440 bool WG0X::readSpiEepromCmd(EthercatCom *com, WG0XSpiEepromCmd &cmd) 01441 { 01442 BOOST_STATIC_ASSERT(sizeof(WG0XSpiEepromCmd) == 3); 01443 if (readMailbox(com, WG0XSpiEepromCmd::SPI_COMMAND_ADDR, &cmd, sizeof(cmd))) 01444 { 01445 ROS_ERROR("Reading SPI command register with mailbox"); 01446 return false; 01447 } 01448 01449 return true; 01450 } 01451 01452 01471 bool WG0X::program(EthercatCom *com, const WG0XActuatorInfo &actutor_info) 01472 { 01473 if (!writeEepromPage(com, ACTUATOR_INFO_PAGE, &actutor_info, sizeof(actutor_info))) 01474 { 01475 ROS_ERROR("Writing actuator infomation to EEPROM"); 01476 return false; 01477 } 01478 01479 return true; 01480 } 01481 01482 01497 bool WG0X::program(EthercatCom *com, const ethercat_hardware::MotorHeatingModelParametersEepromConfig &heating_config) 01498 { 01499 if (!writeEepromPage(com, heating_config.EEPROM_PAGE, &heating_config, sizeof(heating_config))) 01500 { 01501 ROS_ERROR("Writing motor heating model configuration to EEPROM"); 01502 return false; 01503 } 01504 01505 return true; 01506 } 01507 01515 int timediff_ms(const timespec ¤t, const timespec &start) 01516 { 01517 int timediff_ms = (current.tv_sec-start.tv_sec)*1000 // 1000 ms in a sec 01518 + (current.tv_nsec-start.tv_nsec)/1000000; // 1000000 ns in a ms 01519 return timediff_ms; 01520 } 01521 01522 01530 int safe_clock_gettime(clockid_t clk_id, timespec *time) 01531 { 01532 int result = clock_gettime(clk_id, time); 01533 if (result != 0) { 01534 int error = errno; 01535 fprintf(stderr, "safe_clock_gettime : %s\n", strerror(error)); 01536 return result; 01537 } 01538 return result; 01539 } 01540 01541 01549 void safe_usleep(uint32_t usec) 01550 { 01551 assert(usec<1000000); 01552 if (usec>1000000) 01553 usec=1000000; 01554 struct timespec req, rem; 01555 req.tv_sec = 0; 01556 req.tv_nsec = usec*1000; 01557 while (nanosleep(&req, &rem)!=0) { 01558 int error = errno; 01559 fprintf(stderr,"%s : Error : %s\n", __func__, strerror(error)); 01560 if (error != EINTR) { 01561 break; 01562 } 01563 req = rem; 01564 } 01565 return; 01566 } 01567 01568 01569 unsigned SyncMan::baseAddress(unsigned num) 01570 { 01571 assert(num < 8); 01572 return BASE_ADDR + 8 * num; 01573 } 01574 01575 01585 bool SyncMan::readData(EthercatCom *com, EtherCAT_SlaveHandler *sh, EthercatDevice::AddrMode addrMode, unsigned num) 01586 { 01587 return ( EthercatDevice::readData(com, sh, baseAddress(num), this, sizeof(*this), addrMode) == 0); 01588 } 01589 01590 01591 unsigned SyncManActivate::baseAddress(unsigned num) 01592 { 01593 assert(num < 8); 01594 return BASE_ADDR + 8 * num; 01595 } 01596 01606 bool SyncManActivate::writeData(EthercatCom *com, EtherCAT_SlaveHandler *sh, EthercatDevice::AddrMode addrMode, unsigned num) const 01607 { 01608 return ( EthercatDevice::writeData(com, sh, baseAddress(num), this, sizeof(*this), addrMode) == 0); 01609 } 01610 01611 01612 void updateIndexAndWkc(EC_Telegram *tg, EC_Logic *logic) 01613 { 01614 tg->set_idx(logic->get_idx()); 01615 tg->set_wkc(logic->get_wkc()); 01616 } 01617 01618 01619 bool WG0X::verifyDeviceStateForMailboxOperation() 01620 { 01621 // Make sure slave is in correct state to do use mailbox 01622 EC_State state = sh_->get_state(); 01623 if ((state != EC_SAFEOP_STATE) && (state != EC_OP_STATE)) { 01624 fprintf(stderr, "%s : " ERROR_HDR 01625 "cannot do mailbox read in current device state = %d\n", __func__, state); 01626 return false; 01627 } 01628 return true; 01629 } 01630 01631 01641 void WG0X::diagnoseMailboxError(EthercatCom *com) 01642 { 01643 01644 } 01645 01654 bool WG0X::clearReadMailbox(EthercatCom *com) 01655 { 01656 if (!verifyDeviceStateForMailboxOperation()){ 01657 return false; 01658 } 01659 01660 EC_Logic *logic = EC_Logic::instance(); 01661 EC_UINT station_addr = sh_->get_station_address(); 01662 01663 // Create Ethernet packet with two EtherCAT telegrams inside of it : 01664 // - One telegram to read first byte of mailbox 01665 // - One telegram to read last byte of mailbox 01666 unsigned char unused[1] = {0}; 01667 NPRD_Telegram read_start( 01668 logic->get_idx(), 01669 station_addr, 01670 MBX_STATUS_PHY_ADDR, 01671 logic->get_wkc(), 01672 sizeof(unused), 01673 unused); 01674 NPRD_Telegram read_end( 01675 logic->get_idx(), 01676 station_addr, 01677 MBX_STATUS_PHY_ADDR+MBX_STATUS_SIZE-1, 01678 logic->get_wkc(), 01679 sizeof(unused), 01680 unused); 01681 read_start.attach(&read_end); 01682 EC_Ethernet_Frame frame(&read_start); 01683 01684 01685 // Retry sending packet multiple times 01686 bool success=false; 01687 static const unsigned MAX_DROPS = 15; 01688 for (unsigned tries=0; tries<MAX_DROPS; ++tries) { 01689 success = com->txandrx_once(&frame); 01690 if (success) { 01691 break; 01692 } 01693 updateIndexAndWkc(&read_start, logic); 01694 updateIndexAndWkc(&read_end , logic); 01695 } 01696 01697 if (!success) { 01698 fprintf(stderr, "%s : " ERROR_HDR 01699 " too much packet loss\n", __func__); 01700 safe_usleep(100); 01701 return false; 01702 } 01703 01704 // Check result for consistancy 01705 if (read_start.get_wkc() != read_end.get_wkc()) { 01706 fprintf(stderr, "%s : " ERROR_HDR 01707 "read mbx working counters are inconsistant, %d, %d\n", 01708 __func__, read_start.get_wkc(), read_end.get_wkc()); 01709 return false; 01710 } 01711 if (read_start.get_wkc() > 1) { 01712 fprintf(stderr, "%s : " ERROR_HDR 01713 "more than one device (%d) responded \n", __func__, read_start.get_wkc()); 01714 return false; 01715 } 01716 if (read_start.get_wkc() == 1) { 01717 fprintf(stderr, "%s : " WARN_MODE "WARN" STD_MODE 01718 " read mbx contained garbage data\n", __func__); 01719 // Not an error, just warning 01720 } 01721 01722 return true; 01723 } 01724 01725 01726 01736 bool WG0X::waitForReadMailboxReady(EthercatCom *com) 01737 { 01738 // Wait upto 100ms for device to toggle ack 01739 static const int MAX_WAIT_TIME_MS = 100; 01740 int timediff; 01741 unsigned good_results=0; 01742 01743 01744 struct timespec start_time, current_time; 01745 if (safe_clock_gettime(CLOCK_MONOTONIC, &start_time)!=0) { 01746 return false; 01747 } 01748 01749 do { 01750 // Check if mailbox is full by looking at bit 3 of SyncMan status register. 01751 uint8_t SyncManStatus=0; 01752 const unsigned SyncManAddr = 0x805+(MBX_STATUS_SYNCMAN_NUM*8); 01753 if (readData(com, SyncManAddr, &SyncManStatus, sizeof(SyncManStatus), FIXED_ADDR) == 0) { 01754 ++good_results; 01755 const uint8_t MailboxStatusMask = (1<<3); 01756 if (SyncManStatus & MailboxStatusMask) { 01757 return true; 01758 } 01759 } 01760 if (safe_clock_gettime(CLOCK_MONOTONIC, ¤t_time)!=0) { 01761 return false; 01762 } 01763 timediff = timediff_ms(current_time, start_time); 01764 safe_usleep(100); 01765 } while (timediff < MAX_WAIT_TIME_MS); 01766 01767 if (good_results == 0) { 01768 fprintf(stderr, "%s : " ERROR_HDR 01769 " error reading from device\n", __func__); 01770 } else { 01771 fprintf(stderr, "%s : " ERROR_HDR 01772 " error read mbx not full after %d ms\n", __func__, timediff); 01773 } 01774 01775 return false; 01776 } 01777 01778 01788 bool WG0X::waitForWriteMailboxReady(EthercatCom *com) 01789 { 01790 // Wait upto 100ms for device to toggle ack 01791 static const int MAX_WAIT_TIME_MS = 100; 01792 int timediff; 01793 unsigned good_results=0; 01794 01795 01796 struct timespec start_time, current_time; 01797 if (safe_clock_gettime(CLOCK_MONOTONIC, &start_time)!=0) { 01798 return false; 01799 } 01800 01801 do { 01802 // Check if mailbox is full by looking at bit 3 of SyncMan status register. 01803 uint8_t SyncManStatus=0; 01804 const unsigned SyncManAddr = 0x805+(MBX_COMMAND_SYNCMAN_NUM*8); 01805 if (readData(com, SyncManAddr, &SyncManStatus, sizeof(SyncManStatus), FIXED_ADDR) == 0) { 01806 ++good_results; 01807 const uint8_t MailboxStatusMask = (1<<3); 01808 if ( !(SyncManStatus & MailboxStatusMask) ) { 01809 return true; 01810 } 01811 } 01812 if (safe_clock_gettime(CLOCK_MONOTONIC, ¤t_time)!=0) { 01813 return false; 01814 } 01815 timediff = timediff_ms(current_time, start_time); 01816 safe_usleep(100); 01817 } while (timediff < MAX_WAIT_TIME_MS); 01818 01819 if (good_results == 0) { 01820 fprintf(stderr, "%s : " ERROR_HDR 01821 " error reading from device\n", __func__); 01822 } else { 01823 fprintf(stderr, "%s : " ERROR_HDR 01824 " error write mbx not empty after %d ms\n", __func__, timediff); 01825 } 01826 01827 return false; 01828 } 01829 01830 01831 01843 bool WG0X::writeMailboxInternal(EthercatCom *com, void const *data, unsigned length) 01844 { 01845 if (length > MBX_COMMAND_SIZE) { 01846 assert(length <= MBX_COMMAND_SIZE); 01847 return false; 01848 } 01849 01850 // Make sure slave is in correct state to use mailbox 01851 if (!verifyDeviceStateForMailboxOperation()){ 01852 return false; 01853 } 01854 01855 EC_Logic *logic = EC_Logic::instance(); 01856 EC_UINT station_addr = sh_->get_station_address(); 01857 01858 01859 // If there enough savings, split mailbox write up into 2 parts : 01860 // 1. Write of actual data to begining of mbx buffer 01861 // 2. Write of last mbx buffer byte, to complete write 01862 static const unsigned TELEGRAM_OVERHEAD = 50; 01863 bool split_write = (length+TELEGRAM_OVERHEAD) < MBX_COMMAND_SIZE; 01864 01865 unsigned write_length = MBX_COMMAND_SIZE; 01866 if (split_write) { 01867 write_length = length; 01868 } 01869 01870 // Possible do multiple things at once... 01871 // 1. Clear read mailbox by reading both first and last mailbox bytes 01872 // 2. Write data into write mailbox 01873 { 01874 // Build frame with 2-NPRD + 2 NPWR 01875 unsigned char unused[1] = {0}; 01876 NPWR_Telegram write_start( 01877 logic->get_idx(), 01878 station_addr, 01879 MBX_COMMAND_PHY_ADDR, 01880 logic->get_wkc(), 01881 write_length, 01882 (const unsigned char*) data); 01883 NPWR_Telegram write_end( 01884 logic->get_idx(), 01885 station_addr, 01886 MBX_COMMAND_PHY_ADDR+MBX_COMMAND_SIZE-1, 01887 logic->get_wkc(), 01888 sizeof(unused), 01889 unused); 01890 01891 if (split_write) { 01892 write_start.attach(&write_end); 01893 } 01894 01895 EC_Ethernet_Frame frame(&write_start); 01896 01897 // Try multiple times, but remember number of of successful sends 01898 unsigned sends=0; 01899 bool success=false; 01900 for (unsigned tries=0; (tries<10) && !success; ++tries) { 01901 success = com->txandrx_once(&frame); 01902 if (!success) { 01903 updateIndexAndWkc(&write_start, logic); 01904 updateIndexAndWkc(&write_end, logic); 01905 } 01906 ++sends; //EtherCAT_com d/n support split TX and RX class, assume tx part of txandrx always succeeds 01907 /* 01908 int handle = com->tx(&frame); 01909 if (handle > 0) { 01910 ++sends; 01911 success = com->rx(&frame, handle); 01912 } 01913 if (!success) { 01914 updateIndexAndWkc(&write_start, logic); 01915 updateIndexAndWkc(&write_end, logic); 01916 } 01917 */ 01918 } 01919 if (!success) { 01920 fprintf(stderr, "%s : " ERROR_HDR 01921 " too much packet loss\n", __func__); 01922 safe_usleep(100); 01923 return false; 01924 } 01925 01926 if (split_write && (write_start.get_wkc() != write_end.get_wkc())) { 01927 fprintf(stderr, "%s : " ERROR_HDR 01928 " write mbx working counters are inconsistant\n", __func__); 01929 return false; 01930 } 01931 01932 if (write_start.get_wkc() > 1) 01933 { 01934 fprintf(stderr, "%s : " ERROR_HDR 01935 " multiple (%d) devices responded to mailbox write\n", __func__, write_start.get_wkc()); 01936 return false; 01937 } 01938 else if (write_start.get_wkc() != 1) 01939 { 01940 // Write to cmd mbx was refused 01941 if (sends<=1) { 01942 // Packet was only sent once, there must be a problem with slave device 01943 fprintf(stderr, "%s : " ERROR_HDR 01944 " initial mailbox write refused\n", __func__); 01945 safe_usleep(100); 01946 return false; 01947 } else { 01948 // Packet was sent multiple times because a packet drop occured 01949 // If packet drop occured on return path from device, a refusal is acceptable 01950 fprintf(stderr, "%s : " WARN_HDR 01951 " repeated mailbox write refused\n", __func__); 01952 } 01953 } 01954 } 01955 01956 return true; 01957 } 01958 01959 bool WG0X::readMailboxRepeatRequest(EthercatCom *com) 01960 { 01961 bool success = _readMailboxRepeatRequest(com); 01962 ++mailbox_diagnostics_.retries_; 01963 if (!success) { 01964 ++mailbox_diagnostics_.retry_errors_; 01965 } 01966 return success; 01967 } 01968 01969 bool WG0X::_readMailboxRepeatRequest(EthercatCom *com) 01970 { 01971 // Toggle repeat request flag, wait for ack from device 01972 // Returns true if ack is received, false for failure 01973 SyncMan sm; 01974 if (!sm.readData(com, sh_, FIXED_ADDR, MBX_STATUS_SYNCMAN_NUM)) { 01975 fprintf(stderr, "%s : " ERROR_HDR 01976 " could not read status mailbox syncman (1)\n", __func__); 01977 return false; 01978 } 01979 01980 // If device can handle repeat requests, then request and ack bit should already match 01981 if (sm.activate.repeat_request != sm.pdi_control.repeat_ack) { 01982 fprintf(stderr, "%s : " ERROR_HDR 01983 " syncman repeat request and ack do not match\n", __func__); 01984 return false; 01985 } 01986 01987 // Write toggled repeat request,,, wait for ack. 01988 SyncManActivate orig_activate(sm.activate); 01989 sm.activate.repeat_request = ~orig_activate.repeat_request; 01990 if (!sm.activate.writeData(com, sh_, FIXED_ADDR, MBX_STATUS_SYNCMAN_NUM)) { 01991 fprintf(stderr, "%s : " ERROR_HDR 01992 " could not write syncman repeat request\n", __func__); 01993 //ec_mark(sh->getEM(), "could not write syncman repeat request", 1); 01994 return false; 01995 } 01996 01997 // Wait upto 100ms for device to toggle ack 01998 static const int MAX_WAIT_TIME_MS = 100; 01999 int timediff; 02000 02001 struct timespec start_time, current_time; 02002 if (safe_clock_gettime(CLOCK_MONOTONIC, &start_time)!=0) { 02003 return false; 02004 } 02005 02006 do { 02007 if (!sm.readData(com, sh_, FIXED_ADDR, MBX_STATUS_SYNCMAN_NUM)) { 02008 fprintf(stderr, "%s : " ERROR_HDR 02009 " could not read status mailbox syncman (2)\n", __func__); 02010 return false; 02011 } 02012 02013 if (sm.activate.repeat_request == sm.pdi_control.repeat_ack) { 02014 // Device responded, to some checks to make sure it seems to be telling the truth 02015 if (sm.status.mailbox_status != 1) { 02016 fprintf(stderr, "%s : " ERROR_HDR 02017 " got repeat response, but read mailbox is still empty\n", __func__); 02018 //sm.print(WG0X_MBX_Status_Syncman_Num, std::cerr); 02019 return false; 02020 } 02021 return true; 02022 } 02023 02024 if ( (sm.activate.repeat_request) == (orig_activate.repeat_request) ) { 02025 fprintf(stderr, "%s : " ERROR_HDR 02026 " syncman repeat request was changed while waiting for response\n", __func__); 02027 //sm.activate.print(); 02028 //orig_activate.print(); 02029 return false; 02030 } 02031 02032 if (safe_clock_gettime(CLOCK_MONOTONIC, ¤t_time)!=0) { 02033 return false; 02034 } 02035 02036 timediff = timediff_ms(current_time, start_time); 02037 safe_usleep(100); 02038 } while (timediff < MAX_WAIT_TIME_MS); 02039 02040 fprintf(stderr, "%s : " ERROR_HDR 02041 " error repeat request not acknowledged after %d ms\n", __func__, timediff); 02042 return false; 02043 } 02044 02045 02046 02058 bool WG0X::readMailboxInternal(EthercatCom *com, void *data, unsigned length) 02059 { 02060 static const unsigned MAX_TRIES = 10; 02061 static const unsigned MAX_DROPPED = 10; 02062 02063 if (length > MBX_STATUS_SIZE) { 02064 assert(length <= MBX_STATUS_SIZE); 02065 return false; 02066 } 02067 02068 // Make sure slave is in correct state to use mailbox 02069 if (!verifyDeviceStateForMailboxOperation()){ 02070 return false; 02071 } 02072 02073 EC_Logic *logic = EC_Logic::instance(); 02074 EC_UINT station_addr = sh_->get_station_address(); 02075 02076 02077 // If read is small enough : 02078 // 1. read just length bytes in one telegram 02079 // 2. then read last byte to empty mailbox 02080 static const unsigned TELEGRAM_OVERHEAD = 50; 02081 bool split_read = (length+TELEGRAM_OVERHEAD) < MBX_STATUS_SIZE; 02082 02083 unsigned read_length = MBX_STATUS_SIZE; 02084 if (split_read) { 02085 read_length = length; 02086 } 02087 02088 unsigned char unused[1] = {0}; 02089 NPRD_Telegram read_start( 02090 logic->get_idx(), 02091 station_addr, 02092 MBX_STATUS_PHY_ADDR, 02093 logic->get_wkc(), 02094 read_length, 02095 (unsigned char*) data); 02096 NPRD_Telegram read_end( 02097 logic->get_idx(), 02098 station_addr, 02099 MBX_STATUS_PHY_ADDR+MBX_STATUS_SIZE-1, 02100 logic->get_wkc(), 02101 sizeof(unused), 02102 unused); 02103 02104 if (split_read) { 02105 read_start.attach(&read_end); 02106 } 02107 02108 EC_Ethernet_Frame frame(&read_start); 02109 02110 unsigned tries = 0; 02111 unsigned total_dropped =0; 02112 for (tries=0; tries<MAX_TRIES; ++tries) { 02113 02114 // Send read - keep track of how many packets were dropped (for later) 02115 unsigned dropped=0; 02116 for (dropped=0; dropped<MAX_DROPPED; ++dropped) { 02117 if (com->txandrx_once(&frame)) { 02118 break; 02119 } 02120 ++total_dropped; 02121 updateIndexAndWkc(&read_start , logic); 02122 updateIndexAndWkc(&read_end , logic); 02123 } 02124 02125 if (dropped>=MAX_DROPPED) { 02126 fprintf(stderr, "%s : " ERROR_HDR 02127 " too many dropped packets : %d\n", __func__, dropped); 02128 } 02129 02130 if (split_read && (read_start.get_wkc() != read_end.get_wkc())) { 02131 fprintf(stderr, "%s : " ERROR_HDR 02132 "read mbx working counters are inconsistant\n", __func__); 02133 return false; 02134 } 02135 02136 if (read_start.get_wkc() == 0) { 02137 if (dropped == 0) { 02138 fprintf(stderr, "%s : " ERROR_HDR 02139 " inconsistancy : got wkc=%d with no dropped packets\n", 02140 __func__, read_start.get_wkc()); 02141 fprintf(stderr, "total dropped = %d\n", total_dropped); 02142 return false; 02143 } else { 02144 // Packet was dropped after doing read from device,,, 02145 // Ask device to repost data, so it can be read again. 02146 fprintf(stderr, "%s : " WARN_HDR 02147 " asking for read repeat after dropping %d packets\n", __func__, dropped); 02148 if (!readMailboxRepeatRequest(com)) { 02149 return false; 02150 } 02151 continue; 02152 } 02153 } else if (read_start.get_wkc() == 1) { 02154 // Successfull read of status data 02155 break; 02156 } else { 02157 fprintf(stderr, "%s : " ERROR_HDR 02158 " invalid wkc for read : %d\n", __func__, read_start.get_wkc()); 02159 diagnoseMailboxError(com); 02160 return false; 02161 } 02162 } 02163 02164 if (tries >= MAX_TRIES) { 02165 fprintf(stderr, "%s : " ERROR_HDR 02166 " could not get responce from device after %d retries, %d total dropped packets\n", 02167 __func__, tries, total_dropped); 02168 diagnoseMailboxError(com); 02169 return false; 02170 } 02171 02172 return true; 02173 } 02174 02175 02189 int WG0X::readMailbox(EthercatCom *com, unsigned address, void *data, unsigned length) 02190 { 02191 if (!lockMailbox()) 02192 return -1; 02193 02194 int result = readMailbox_(com, address, data, length); 02195 if (result != 0) { 02196 ++mailbox_diagnostics_.read_errors_; 02197 } 02198 02199 unlockMailbox(); 02200 return result; 02201 } 02202 02208 int WG0X::readMailbox_(EthercatCom *com, unsigned address, void *data, unsigned length) 02209 { 02210 // Make sure slave is in correct state to use mailbox 02211 if (!verifyDeviceStateForMailboxOperation()){ 02212 return false; 02213 } 02214 02215 // 1. Clear read (status) mailbox by reading it first 02216 if (!clearReadMailbox(com)) 02217 { 02218 fprintf(stderr, "%s : " ERROR_HDR 02219 " clearing read mbx\n", __func__); 02220 return -1; 02221 } 02222 02223 // 2. Put a (read) request into command mailbox 02224 { 02225 WG0XMbxCmd cmd; 02226 if (!cmd.build(address, length, LOCAL_BUS_READ, sh_->get_mbx_counter(), data)) 02227 { 02228 fprintf(stderr, "%s : " ERROR_HDR 02229 " builing mbx header\n", __func__); 02230 return -1; 02231 } 02232 02233 if (!writeMailboxInternal(com, &cmd.hdr_, sizeof(cmd.hdr_))) 02234 { 02235 fprintf(stderr, "%s : " ERROR_HDR " write of cmd failed\n", __func__); 02236 return -1; 02237 } 02238 } 02239 02240 // Wait for result (in read mailbox) to become ready 02241 if (!waitForReadMailboxReady(com)) 02242 { 02243 fprintf(stderr, "%s : " ERROR_HDR 02244 "waiting for read mailbox\n", __func__); 02245 return -1; 02246 } 02247 02248 // Read result back from mailbox. 02249 // It could take the FPGA some time to respond to a request. 02250 // Since the read mailbox is initiall cleared, any read to the mailbox 02251 // should be refused (WKC==0) until WG0x FPGA has written it result into it. 02252 // NOTE: For this to work the mailbox syncmanagers must be set up. 02253 // TODO 1: Packets may get lost on return route to device. 02254 // In this case, the device will keep responding to the repeated packets with WKC=0. 02255 // To work correctly, the repeat request bit needs to be toggled. 02256 // TODO 2: Need a better method to determine if data read from status mailbox. 02257 // is the right data, or just junk left over from last time. 02258 { 02259 WG0XMbxCmd stat; 02260 memset(&stat,0,sizeof(stat)); 02261 // Read data + 1byte checksum from mailbox 02262 if (!readMailboxInternal(com, &stat, length+1)) 02263 { 02264 fprintf(stderr, "%s : " ERROR_HDR " read failed\n", __func__); 02265 return -1; 02266 } 02267 02268 if (computeChecksum(&stat, length+1) != 0) 02269 { 02270 fprintf(stderr, "%s : " ERROR_HDR 02271 "checksum error reading mailbox data\n", __func__); 02272 fprintf(stderr, "length = %d\n", length); 02273 return -1; 02274 } 02275 memcpy(data, &stat, length); 02276 } 02277 02278 return 0; 02279 02280 02281 } 02282 02283 bool WG0X::lockMailbox() 02284 { 02285 int error = pthread_mutex_lock(&mailbox_lock_); 02286 if (error != 0) { 02287 fprintf(stderr, "%s : " ERROR_HDR " getting mbx lock\n", __func__); 02288 ++mailbox_diagnostics_.lock_errors_; 02289 return false; 02290 } 02291 return true; 02292 } 02293 02294 void WG0X::unlockMailbox() 02295 { 02296 int error = pthread_mutex_unlock(&mailbox_lock_); 02297 if (error != 0) { 02298 fprintf(stderr, "%s : " ERROR_HDR " freeing mbx lock\n", __func__); 02299 ++mailbox_diagnostics_.lock_errors_; 02300 } 02301 } 02302 02303 bool WG0X::lockWG0XDiagnostics() 02304 { 02305 int error = pthread_mutex_lock(&wg0x_diagnostics_lock_); 02306 if (error != 0) { 02307 fprintf(stderr, "%s : " ERROR_HDR " getting diagnostics lock\n", __func__); 02308 // update error counters even if we didn't get lock 02309 ++wg0x_collect_diagnostics_.lock_errors_; 02310 return false; 02311 } 02312 return true; 02313 } 02314 02315 bool WG0X::tryLockWG0XDiagnostics() 02316 { 02317 int error = pthread_mutex_trylock(&wg0x_diagnostics_lock_); 02318 if (error == EBUSY) { 02319 return false; 02320 } 02321 else if (error != 0) { 02322 fprintf(stderr, "%s : " ERROR_HDR " getting diagnostics lock\n", __func__); 02323 // update error counters even if we didn't get lock 02324 ++wg0x_collect_diagnostics_.lock_errors_; 02325 return false; 02326 } 02327 return true; 02328 } 02329 02330 void WG0X::unlockWG0XDiagnostics() 02331 { 02332 int error = pthread_mutex_unlock(&wg0x_diagnostics_lock_); 02333 if (error != 0) { 02334 fprintf(stderr, "%s : " ERROR_HDR " freeing diagnostics lock\n", __func__); 02335 ++wg0x_collect_diagnostics_.lock_errors_; 02336 } 02337 } 02338 02339 02352 int WG0X::writeMailbox(EthercatCom *com, unsigned address, void const *data, unsigned length) 02353 { 02354 if (!lockMailbox()) 02355 return -1; 02356 02357 int result = writeMailbox_(com, address, data, length); 02358 if (result != 0) { 02359 ++mailbox_diagnostics_.write_errors_; 02360 } 02361 02362 unlockMailbox(); 02363 02364 return result; 02365 } 02366 02372 int WG0X::writeMailbox_(EthercatCom *com, unsigned address, void const *data, unsigned length) 02373 { 02374 // Make sure slave is in correct state to use mailbox 02375 if (!verifyDeviceStateForMailboxOperation()){ 02376 return -1; 02377 } 02378 02379 // Build message and put it into write mailbox 02380 { 02381 WG0XMbxCmd cmd; 02382 if (!cmd.build(address, length, LOCAL_BUS_WRITE, sh_->get_mbx_counter(), data)) { 02383 fprintf(stderr, "%s : " ERROR_HDR " builing mbx header\n", __func__); 02384 return -1; 02385 } 02386 02387 unsigned write_length = sizeof(cmd.hdr_)+length+sizeof(cmd.checksum_); 02388 if (!writeMailboxInternal(com, &cmd, write_length)) { 02389 fprintf(stderr, "%s : " ERROR_HDR " write failed\n", __func__); 02390 diagnoseMailboxError(com); 02391 return -1; 02392 } 02393 } 02394 02395 // TODO: Change slave firmware so that we can verify that localbus write was truly executed 02396 // Checking that device emptied write mailbox will have to suffice for now. 02397 if (!waitForWriteMailboxReady(com)) { 02398 fprintf(stderr, "%s : " ERROR_HDR 02399 "write mailbox\n", __func__); 02400 } 02401 02402 return 0; 02403 } 02404 02405 02406 #define CHECK_SAFETY_BIT(bit) \ 02407 do { if (status & SAFETY_##bit) { \ 02408 str += prefix + #bit; \ 02409 prefix = ", "; \ 02410 } } while(0) 02411 02412 string WG0X::safetyDisableString(uint8_t status) 02413 { 02414 string str, prefix; 02415 02416 if (status & SAFETY_DISABLED) 02417 { 02418 CHECK_SAFETY_BIT(DISABLED); 02419 CHECK_SAFETY_BIT(UNDERVOLTAGE); 02420 CHECK_SAFETY_BIT(OVER_CURRENT); 02421 CHECK_SAFETY_BIT(BOARD_OVER_TEMP); 02422 CHECK_SAFETY_BIT(HBRIDGE_OVER_TEMP); 02423 CHECK_SAFETY_BIT(OPERATIONAL); 02424 CHECK_SAFETY_BIT(WATCHDOG); 02425 } 02426 else 02427 str = "ENABLED"; 02428 02429 return str; 02430 } 02431 02432 string WG0X::modeString(uint8_t mode) 02433 { 02434 string str, prefix; 02435 if (mode) { 02436 if (mode & MODE_ENABLE) { 02437 str += prefix + "ENABLE"; 02438 prefix = ", "; 02439 } 02440 if (mode & MODE_CURRENT) { 02441 str += prefix + "CURRENT"; 02442 prefix = ", "; 02443 } 02444 if (mode & MODE_UNDERVOLTAGE) { 02445 str += prefix + "UNDERVOLTAGE"; 02446 prefix = ", "; 02447 } 02448 if (mode & MODE_SAFETY_RESET) { 02449 str += prefix + "SAFETY_RESET"; 02450 prefix = ", "; 02451 } 02452 if (mode & MODE_SAFETY_LOCKOUT) { 02453 str += prefix + "SAFETY_LOCKOUT"; 02454 prefix = ", "; 02455 } 02456 if (mode & MODE_RESET) { 02457 str += prefix + "RESET"; 02458 prefix = ", "; 02459 } 02460 } else { 02461 str = "OFF"; 02462 } 02463 return str; 02464 } 02465 02466 void WG0X::publishMailboxDiagnostics(diagnostic_updater::DiagnosticStatusWrapper &d) 02467 { 02468 if (lockMailbox()) { 02469 mailbox_publish_diagnostics_ = mailbox_diagnostics_; 02470 unlockMailbox(); 02471 } 02472 02473 MbxDiagnostics const &m(mailbox_publish_diagnostics_); 02474 d.addf("Mailbox Write Errors", "%d", m.write_errors_); 02475 d.addf("Mailbox Read Errors", "%d", m.read_errors_); 02476 d.addf("Mailbox Retries", "%d", m.retries_); 02477 d.addf("Mailbox Retry Errors", "%d", m.retry_errors_); 02478 } 02479 02480 void WG0X::publishGeneralDiagnostics(diagnostic_updater::DiagnosticStatusWrapper &d) 02481 { 02482 // If possible, copy new diagnositics from collection thread, into diagnostics thread 02483 if (tryLockWG0XDiagnostics()) { 02484 wg0x_publish_diagnostics_ = wg0x_collect_diagnostics_; 02485 unlockWG0XDiagnostics(); 02486 } 02487 02488 if (too_many_dropped_packets_) 02489 d.mergeSummary(d.ERROR, "Too many dropped packets"); 02490 02491 if (status_checksum_error_) 02492 { 02493 d.mergeSummary(d.ERROR, "Checksum error on status data"); 02494 } 02495 02496 if (wg0x_publish_diagnostics_.first_) 02497 { 02498 d.mergeSummary(d.WARN, "Have not yet collected WG0X diagnostics"); 02499 } 02500 else if (!wg0x_publish_diagnostics_.valid_) 02501 { 02502 d.mergeSummary(d.WARN, "Could not collect WG0X diagnostics"); 02503 } 02504 02505 WG0XDiagnostics const &p(wg0x_publish_diagnostics_); 02506 WG0XSafetyDisableStatus const &s(p.safety_disable_status_); 02507 d.addf("Status Checksum Error Count", "%d", p.checksum_errors_); 02508 d.addf("Safety Disable Status", "%s (%02x)", safetyDisableString(s.safety_disable_status_).c_str(), s.safety_disable_status_); 02509 d.addf("Safety Disable Status Hold", "%s (%02x)", safetyDisableString(s.safety_disable_status_hold_).c_str(), s.safety_disable_status_hold_); 02510 d.addf("Safety Disable Count", "%d", p.safety_disable_total_); 02511 d.addf("Undervoltage Count", "%d", p.undervoltage_total_); 02512 d.addf("Over Current Count", "%d", p.over_current_total_); 02513 d.addf("Board Over Temp Count", "%d", p.board_over_temp_total_); 02514 d.addf("Bridge Over Temp Count", "%d", p.bridge_over_temp_total_); 02515 d.addf("Operate Disable Count", "%d", p.operate_disable_total_); 02516 d.addf("Watchdog Disable Count", "%d", p.watchdog_disable_total_); 02517 02518 if (in_lockout_) 02519 { 02520 uint8_t status = s.safety_disable_status_hold_; 02521 string prefix(": "); 02522 string str("Safety Lockout"); 02523 CHECK_SAFETY_BIT(UNDERVOLTAGE); 02524 CHECK_SAFETY_BIT(OVER_CURRENT); 02525 CHECK_SAFETY_BIT(BOARD_OVER_TEMP); 02526 CHECK_SAFETY_BIT(HBRIDGE_OVER_TEMP); 02527 CHECK_SAFETY_BIT(OPERATIONAL); 02528 CHECK_SAFETY_BIT(WATCHDOG); 02529 d.mergeSummary(d.ERROR, str); 02530 } 02531 02532 if (timestamp_jump_detected_ && (s.safety_disable_status_hold_ & SAFETY_OPERATIONAL)) 02533 { 02534 fpga_internal_reset_detected_ = true; 02535 } 02536 02537 if (fpga_internal_reset_detected_) 02538 { 02539 d.mergeSummaryf(d.ERROR, "FPGA internal reset detected"); 02540 } 02541 02542 if (timestamp_jump_detected_) 02543 { 02544 d.mergeSummaryf(d.WARN, "Timestamp jumped"); 02545 } 02546 02547 { 02548 02549 const WG0XDiagnosticsInfo &di(p.diagnostics_info_); 02550 //d.addf("PDO Command IRQ Count", "%d", di.pdo_command_irq_count_); 02551 d.addf("MBX Command IRQ Count", "%d", di.mbx_command_irq_count_); 02552 d.addf("PDI Timeout Error Count", "%d", di.pdi_timeout_error_count_); 02553 d.addf("PDI Checksum Error Count", "%d", di.pdi_checksum_error_count_); 02554 unsigned product = sh_->get_product_code(); 02555 02556 // Current scale 02557 if ((product == WG05_PRODUCT_CODE) && (board_major_ == 1)) 02558 { 02559 // WG005B measure current going into and out-of H-bridge (not entire board) 02560 static const double WG005B_SUPPLY_CURRENT_SCALE = (1.0 / (8152.0 * 0.851)) * 4.0; 02561 double bridge_supply_current = double(di.supply_current_in_) * WG005B_SUPPLY_CURRENT_SCALE; 02562 d.addf("Bridge Supply Current", "%f", bridge_supply_current); 02563 } 02564 else if ((product == WG05_PRODUCT_CODE) || (product == WG021_PRODUCT_CODE)) 02565 { 02566 // WG005[CDEF] measures curret going into entire board. It cannot measure negative (regenerative) current values. 02567 // WG021A == WG005E, WG021B == WG005F 02568 static const double WG005_SUPPLY_CURRENT_SCALE = ((82.0 * 2.5) / (0.01 * 5100.0 * 32768.0)); 02569 double supply_current = double(di.supply_current_in_) * WG005_SUPPLY_CURRENT_SCALE; 02570 d.addf("Supply Current", "%f", supply_current); 02571 } 02572 d.addf("Configured Offset A", "%f", config_info_.nominal_current_scale_ * di.config_offset_current_A_); 02573 d.addf("Configured Offset B", "%f", config_info_.nominal_current_scale_ * di.config_offset_current_B_); 02574 } 02575 } 02576 02577 02578 02579 void WG0X::diagnostics(diagnostic_updater::DiagnosticStatusWrapper &d, unsigned char *buffer) 02580 { 02581 WG0XStatus *status = (WG0XStatus *)(buffer + command_size_); 02582 02583 stringstream str; 02584 str << "EtherCAT Device (" << actuator_info_.name_ << ")"; 02585 d.name = str.str(); 02586 char serial[32]; 02587 snprintf(serial, sizeof(serial), "%d-%05d-%05d", config_info_.product_id_ / 100000 , config_info_.product_id_ % 100000, config_info_.device_serial_number_); 02588 d.hardware_id = serial; 02589 02590 if (!has_error_) 02591 d.summary(d.OK, "OK"); 02592 02593 d.clear(); 02594 d.add("Configuration", config_info_.configuration_status_ ? "good" : "error loading configuration"); 02595 d.add("Name", actuator_info_.name_); 02596 d.addf("Position", "%02d", sh_->get_ring_position()); 02597 d.addf("Product code", 02598 "WG0%d (%d) Firmware Revision %d.%02d, PCB Revision %c.%02d", 02599 sh_->get_product_code() == WG05_PRODUCT_CODE ? 5 : 6, 02600 sh_->get_product_code(), fw_major_, fw_minor_, 02601 'A' + board_major_, board_minor_); 02602 02603 d.add("Robot", actuator_info_.robot_name_); 02604 d.addf("Motor", "%s %s", actuator_info_.motor_make_, actuator_info_.motor_model_); 02605 d.add("Serial Number", serial); 02606 d.addf("Nominal Current Scale", "%f", config_info_.nominal_current_scale_); 02607 d.addf("Nominal Voltage Scale", "%f", config_info_.nominal_voltage_scale_); 02608 d.addf("HW Max Current", "%f", config_info_.absolute_current_limit_ * config_info_.nominal_current_scale_); 02609 02610 d.addf("SW Max Current", "%f", actuator_info_.max_current_); 02611 d.addf("Speed Constant", "%f", actuator_info_.speed_constant_); 02612 d.addf("Resistance", "%f", actuator_info_.resistance_); 02613 d.addf("Motor Torque Constant", "%f", actuator_info_.motor_torque_constant_); 02614 d.addf("Pulses Per Revolution", "%d", actuator_info_.pulses_per_revolution_); 02615 d.addf("Encoder Reduction", "%f", actuator_info_.encoder_reduction_); 02616 02617 publishGeneralDiagnostics(d); 02618 publishMailboxDiagnostics(d); 02619 02620 d.addf("Calibration Offset", "%f", cached_zero_offset_); 02621 d.addf("Calibration Status", "%s", 02622 (calibration_status_ == NO_CALIBRATION) ? "No calibration" : 02623 (calibration_status_ == CONTROLLER_CALIBRATION) ? "Calibrated by controller" : 02624 (calibration_status_ == SAVED_CALIBRATION) ? "Using saved calibration" : "UNKNOWN"); 02625 02626 d.addf("Watchdog Limit", "%dms", config_info_.watchdog_limit_); 02627 d.add("Mode", modeString(status->mode_)); 02628 d.addf("Digital out", "%d", status->digital_out_); 02629 d.addf("Programmed pwm value", "%d", status->programmed_pwm_value_); 02630 d.addf("Programmed current", "%f", status->programmed_current_ * config_info_.nominal_current_scale_); 02631 d.addf("Measured current", "%f", status->measured_current_ * config_info_.nominal_current_scale_); 02632 d.addf("Timestamp", "%u", status->timestamp_); 02633 d.addf("Encoder count", "%d", status->encoder_count_); 02634 d.addf("Encoder index pos", "%d", status->encoder_index_pos_); 02635 d.addf("Num encoder_errors", "%d", status->num_encoder_errors_); 02636 d.addf("Encoder status", "%d", status->encoder_status_); 02637 d.addf("Calibration reading", "%d", status->calibration_reading_); 02638 d.addf("Last calibration rising edge", "%d", status->last_calibration_rising_edge_); 02639 d.addf("Last calibration falling edge", "%d", status->last_calibration_falling_edge_); 02640 d.addf("Board temperature", "%f", 0.0078125 * status->board_temperature_); 02641 d.addf("Max board temperature", "%f", 0.0078125 * max_board_temperature_); 02642 d.addf("Bridge temperature", "%f", 0.0078125 * status->bridge_temperature_); 02643 d.addf("Max bridge temperature", "%f", 0.0078125 * max_bridge_temperature_); 02644 d.addf("Supply voltage", "%f", status->supply_voltage_ * config_info_.nominal_voltage_scale_); 02645 d.addf("Motor voltage", "%f", status->motor_voltage_ * config_info_.nominal_voltage_scale_); 02646 d.addf("Current Loop Kp", "%d", config_info_.current_loop_kp_); 02647 d.addf("Current Loop Ki", "%d", config_info_.current_loop_ki_); 02648 02649 if (motor_model_) 02650 { 02651 motor_model_->diagnostics(d); 02652 if (disable_motor_model_checking_) 02653 { 02654 d.mergeSummaryf(d.WARN, "Motor model disabled"); 02655 } 02656 } 02657 02658 if (motor_heating_model_.get() != NULL) 02659 { 02660 motor_heating_model_->diagnostics(d); 02661 } 02662 02663 if (last_num_encoder_errors_ != status->num_encoder_errors_) 02664 { 02665 d.mergeSummaryf(d.WARN, "Encoder errors detected"); 02666 } 02667 02668 d.addf("Packet count", "%d", status->packet_count_); 02669 02670 d.addf("Drops", "%d", drops_); 02671 d.addf("Consecutive Drops", "%d", consecutive_drops_); 02672 d.addf("Max Consecutive Drops", "%d", max_consecutive_drops_); 02673 02674 unsigned numPorts = (sh_->get_product_code()==WG06_PRODUCT_CODE) ? 1 : 2; // WG006 has 1 port, WG005 has 2 02675 EthercatDevice::ethercatDiagnostics(d, numPorts); 02676 } 02677