diff --git a/bl_force_torque_sensor_ros/CMakeLists.txt b/bl_force_torque_sensor_ros/CMakeLists.txt new file mode 100644 index 000000000..6eb814fc6 --- /dev/null +++ b/bl_force_torque_sensor_ros/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.3) +project(bl_force_torque_sensor_ros) + +find_package(catkin REQUIRED COMPONENTS + message_generation +) + +add_message_files( + FILES + AmplifierOutputRawArray.msg +) + +generate_messages() + +catkin_package( + CATKIN_DEPENDS message_runtime +) + +file(GLOB SCRIPT_PROGRAMS node_scripts/*.py) +catkin_install_python(PROGRAMS ${SCRIPT_PROGRAMS} + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/node_scripts +) + +install(DIRECTORY arduino circuits config launch + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} + USE_SOURCE_PERMISSIONS +) diff --git a/bl_force_torque_sensor_ros/README.md b/bl_force_torque_sensor_ros/README.md new file mode 100644 index 000000000..1b5e7ad79 --- /dev/null +++ b/bl_force_torque_sensor_ros/README.md @@ -0,0 +1,59 @@ +# bl_force_torque_sensor_ros + +ROS driver package for [BL force torque sensor](https://www.bl-autotec.co.jp/products/index.php?cid=5). + +## Circuit + +Before using this package, you have to prepare a circuit connecting your sensor and PC. + +### Circuit using Arduino and [ADS131M04](https://www.ti.com/product/ADS131M04) as ADC for sensor with RMD amplifier + +![](circuits/rmd_ads131m04_arduino/schematic.jpg) + +## How to install + +- Install dependency of `bl_force_torque_sensor_ros` (e.g., by `rosdep install`) and build `bl_force_torque_sensor_ros` (e.g., by `catkin build`) + - See http://wiki.ros.org/rosdep#Install_dependency_of_all_packages_in_the_workspace for `rosdep` usage + + For example: + ```bash + mkdir -p ~/ws_bl_force_torque_sensor_ros/src + cd ~/ws_bl_force_torque_sensor_ros/src + git clone https://github.com/pazeshun/jsk_3rdparty.git -b bl_force_torque_sensor_ros + rosdep install -y -r --ignore-src --from-paths jsk_3rdparty/bl_force_torque_sensor_ros + cd .. + catkin build bl_force_torque_sensor_ros + source ~/ws_bl_force_torque_sensor_ros/devel/setup.bash # Do this every time you open a new terminal + ``` + +- If you prepare a circuit using Arduino, + - [Setup Arduino IDE for rosserial](http://wiki.ros.org/rosserial_arduino/Tutorials/Arduino%20IDE%20Setup#Install_ros_lib_into_the_Arduino_Environment). Note that you must do it after sourcing `setup.bash` of the workspace including `bl_force_torque_sensor_ros`. If you did it without sourcing correctly, do it again + - Upload corresponding firmware to your Arduino. E.g., upload `arduino/sensor_driver_raw_rmd_ads131m04/sensor_driver_raw_rmd_ads131m04.ino` when your circuit uses ADS131M04 for RMD amplifier + - The firmware may require additional libraries + - `sensor_driver_raw_rmd_ads131m04.ino`: search and install [CRC](https://www.arduino.cc/reference/en/libraries/crc/) on library manager of Arduino IDE + +## How to use + +To run sample, you have to specify the calibration matrix of your sensor. +You can pass it via command line, but it may be easier to write and run a simple launch file like: +```xml:my_sample.launch + + + + + + + + + + + + + + +``` diff --git a/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/ADS131M04.cpp b/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/ADS131M04.cpp new file mode 100644 index 000000000..d9493e47d --- /dev/null +++ b/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/ADS131M04.cpp @@ -0,0 +1,537 @@ +/* + Based on https://github.com/LucasEtchezuri/Arduino-ADS131M04/blob/ed879ecd0da5ac0d1dfa0494ff65b1e7ad4ae6d5/ADS131M04.cpp + We cannot use it as is because it is not correctly compiled on default Arduino IDE targeting original Arduinos (e.g., Arduino Uno, Nano): + - It passes arguments to SPI.begin(), while original SPI.begin() does not accept arguments + - https://github.com/LucasEtchezuri/Arduino-ADS131M04/issues/1 + - https://www.arduino.cc/reference/en/language/functions/communication/spi/begin/ + - It operates bit shift greater than variable width. This causes undefined behavior + - https://github.com/LucasEtchezuri/Arduino-ADS131M04/issues/1 + - https://mfumi.hatenadiary.org/entry/20111217/1324137736 + Additional changes: + - Call SPI.beginTransaction() and SPI.endTransaction() every time to accept other SPI devices + - https://stupiddog.jp/note/archives/976 + - Set chip select (CS) high in begin() to accept other SPI devices + - Fix some typos & Spanish comments + - Use CRC to detect SPI communication error + - Enable to change SPI speed +*/ + +#include "Arduino.h" +#include "ADS131M04.h" +#include "SPI.h" +#include "CRC.h" // Search and install CRC on library manager of Arduino IDE +// https://www.arduino.cc/reference/en/libraries/crc/ + +ADS131M04::ADS131M04() +{ +} + +uint8_t ADS131M04::writeRegister(uint8_t address, uint16_t value) +{ + uint16_t res; + uint8_t addressRcv; + uint8_t bytesRcv; + uint16_t cmd = 0; + + SPI.beginTransaction(SPI_SETTINGS); + digitalWrite(ADS131M04_CS_PIN, LOW); + delayMicroseconds(1); + + cmd = (CMD_WRITE_REG) | ((uint16_t)address << 7) | 0; + + //res = SPI.transfer16(cmd); + SPI.transfer16(cmd); + SPI.transfer(0x00); + + SPI.transfer16(value); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + res = SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + delayMicroseconds(1); + digitalWrite(ADS131M04_CS_PIN, HIGH); + SPI.endTransaction(); + + addressRcv = (res & REGMASK_CMD_READ_REG_ADDRESS) >> 7; + bytesRcv = (res & REGMASK_CMD_READ_REG_BYTES); + + if (addressRcv == address) + { + return bytesRcv + 1; + } + return 0; +} + +void ADS131M04::writeRegisterMasked(uint8_t address, uint16_t value, uint16_t mask) +{ + // Write a value to the register, applying the mask to touch only the necessary bits. + // It does not perform the bit shift, the shifted value must be moved to the correct position. + + // Read the current contents of the register + uint16_t register_contents = readRegister(address); + + // Bit by bit change of the mask (1 in the bits not to be touched and 0 in the bits to be modified). + // AND is performed on the current contents of the register. There are "0" in the part to be modified. + register_contents = register_contents & ~mask; + + // OR is performed with the value to be loaded into the register. Note that the value must be in the correct position (shitf). + register_contents = register_contents | value; + + // Rewrite the register + writeRegister(address, register_contents); +} + +uint16_t ADS131M04::readRegister(uint8_t address) +{ + uint16_t cmd; + uint16_t data; + + cmd = CMD_READ_REG | ((uint16_t)address << 7 | 0); + + SPI.beginTransaction(SPI_SETTINGS); + digitalWrite(ADS131M04_CS_PIN, LOW); + delayMicroseconds(1); + + //data = SPI.transfer16(cmd); + SPI.transfer16(cmd); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + data = SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + SPI.transfer16(0x0000); + SPI.transfer(0x00); + + delayMicroseconds(1); + digitalWrite(ADS131M04_CS_PIN, HIGH); + SPI.endTransaction(); + return data; +} + +void ADS131M04::begin(uint8_t cs_pin, uint8_t drdy_pin, uint32_t spi_speed) +{ + // Set pins up + ADS131M04_CS_PIN = cs_pin; + ADS131M04_DRDY_PIN = drdy_pin; + + SPI.begin(); + // Configure chip select as an output + pinMode(ADS131M04_CS_PIN, OUTPUT); + // Set chip select high to accept other SPI devices + digitalWrite(ADS131M04_CS_PIN, HIGH); + delayMicroseconds(1); // Should be longer than t_w(CSH)? + // Configure DRDY as an input + pinMode(ADS131M04_DRDY_PIN, INPUT); + + // Store SPI settings + SPI_SETTINGS = SPISettings(spi_speed, MSBFIRST, SPI_MODE1); +} + +int8_t ADS131M04::isDataReadySoft(byte channel) +{ + if (channel == 0) + { + return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY0); + } + else if (channel == 1) + { + return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY1); + } + else if (channel == 2) + { + return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY2); + } + else if (channel == 3) + { + return (readRegister(REG_STATUS) & REGMASK_STATUS_DRDY3); + } + else + { + return -1; + } +} + +bool ADS131M04::isResetStatus(void) +{ + return (readRegister(REG_STATUS) & REGMASK_STATUS_RESET); +} + +bool ADS131M04::isLockSPI(void) +{ + return (readRegister(REG_STATUS) & REGMASK_STATUS_LOCK); +} + +bool ADS131M04::setDrdyFormat(uint8_t drdyFormat) +{ + if (drdyFormat > 1) + { + return false; + } + else + { + writeRegisterMasked(REG_MODE, drdyFormat, REGMASK_MODE_DRDY_FMT); + return true; + } +} + +bool ADS131M04::setDrdyStateWhenUnavailable(uint8_t drdyState) +{ + if (drdyState > 1) + { + return false; + } + else + { + writeRegisterMasked(REG_MODE, drdyState < 1, REGMASK_MODE_DRDY_HiZ); + return true; + } +} + +bool ADS131M04::setPowerMode(uint8_t powerMode) +{ + if (powerMode > 3) + { + return false; + } + else + { + writeRegisterMasked(REG_CLOCK, powerMode, REGMASK_CLOCK_PWR); + return true; + } +} + +bool ADS131M04::setOsr(uint16_t osr) +{ + if (osr > 7) + { + return false; + } + else + { + writeRegisterMasked(REG_CLOCK, osr << 2 , REGMASK_CLOCK_OSR); + return true; + } +} + +bool ADS131M04::setChannelEnable(uint8_t channel, uint16_t enable) +{ + if (channel > 3) + { + return false; + } + if (channel == 0) + { + writeRegisterMasked(REG_CLOCK, enable << 8, REGMASK_CLOCK_CH0_EN); + return true; + } + else if (channel == 1) + { + writeRegisterMasked(REG_CLOCK, enable << 9, REGMASK_CLOCK_CH1_EN); + return true; + } + else if (channel == 2) + { + writeRegisterMasked(REG_CLOCK, enable << 10, REGMASK_CLOCK_CH2_EN); + return true; + } + else if (channel == 3) + { + writeRegisterMasked(REG_CLOCK, enable << 11, REGMASK_CLOCK_CH3_EN); + return true; + } +} + +bool ADS131M04::setChannelPGA(uint8_t channel, uint16_t pga) +{ + if (channel > 3) + { + return false; + } + if (channel == 0) + { + writeRegisterMasked(REG_GAIN, pga, REGMASK_GAIN_PGAGAIN0); + return true; + } + else if (channel == 1) + { + writeRegisterMasked(REG_GAIN, pga << 4, REGMASK_GAIN_PGAGAIN1); + return true; + } + else if (channel == 2) + { + writeRegisterMasked(REG_GAIN, pga << 8, REGMASK_GAIN_PGAGAIN2); + return true; + } + else if (channel == 3) + { + writeRegisterMasked(REG_GAIN, pga << 12, REGMASK_GAIN_PGAGAIN3); + return true; + } +} + +void ADS131M04::setGlobalChop(uint16_t global_chop) +{ + writeRegisterMasked(REG_CFG, global_chop << 8, REGMASK_CFG_GC_EN); +} + +void ADS131M04::setGlobalChopDelay(uint16_t delay) +{ + writeRegisterMasked(REG_CFG, delay << 9, REGMASK_CFG_GC_DLY); +} + +bool ADS131M04::setInputChannelSelection(uint8_t channel, uint8_t input) +{ + if (channel > 3) + { + return false; + } + if (channel == 0) + { + writeRegisterMasked(REG_CH0_CFG, input, REGMASK_CHX_CFG_MUX); + return true; + } + else if (channel == 1) + { + writeRegisterMasked(REG_CH1_CFG, input, REGMASK_CHX_CFG_MUX); + return true; + } + else if (channel == 2) + { + writeRegisterMasked(REG_CH2_CFG, input, REGMASK_CHX_CFG_MUX); + return true; + } + else if (channel == 3) + { + writeRegisterMasked(REG_CH3_CFG, input, REGMASK_CHX_CFG_MUX); + return true; + } +} + +bool ADS131M04::setChannelOffsetCalibration(uint8_t channel, int32_t offset) +{ + + uint16_t MSB = offset >> 8; + uint8_t LSB = offset; + + if (channel > 3) + { + return false; + } + if (channel == 0) + { + writeRegisterMasked(REG_CH0_OCAL_MSB, MSB, 0xFFFF); + writeRegisterMasked(REG_CH0_OCAL_LSB, (uint16_t)LSB << 8, REGMASK_CHX_OCAL0_LSB); + return true; + } + else if (channel == 1) + { + writeRegisterMasked(REG_CH1_OCAL_MSB, MSB, 0xFFFF); + writeRegisterMasked(REG_CH1_OCAL_LSB, (uint16_t)LSB << 8, REGMASK_CHX_OCAL0_LSB); + return true; + } + else if (channel == 2) + { + writeRegisterMasked(REG_CH2_OCAL_MSB, MSB, 0xFFFF); + writeRegisterMasked(REG_CH2_OCAL_LSB, (uint16_t)LSB << 8, REGMASK_CHX_OCAL0_LSB); + return true; + } + else if (channel == 3) + { + writeRegisterMasked(REG_CH3_OCAL_MSB, MSB, 0xFFFF); + writeRegisterMasked(REG_CH3_OCAL_LSB, (uint16_t)LSB << 8 , REGMASK_CHX_OCAL0_LSB); + return true; + } +} + +bool ADS131M04::setChannelGainCalibration(uint8_t channel, uint32_t gain) +{ + + uint16_t MSB = gain >> 8; + uint8_t LSB = gain; + + if (channel > 3) + { + return false; + } + if (channel == 0) + { + writeRegisterMasked(REG_CH0_GCAL_MSB, MSB, 0xFFFF); + writeRegisterMasked(REG_CH0_GCAL_LSB, (uint16_t)LSB << 8, REGMASK_CHX_GCAL0_LSB); + return true; + } + else if (channel == 1) + { + writeRegisterMasked(REG_CH1_GCAL_MSB, MSB, 0xFFFF); + writeRegisterMasked(REG_CH1_GCAL_LSB, (uint16_t)LSB << 8, REGMASK_CHX_GCAL0_LSB); + return true; + } + else if (channel == 2) + { + writeRegisterMasked(REG_CH2_GCAL_MSB, MSB, 0xFFFF); + writeRegisterMasked(REG_CH2_GCAL_LSB, (uint16_t)LSB << 8, REGMASK_CHX_GCAL0_LSB); + return true; + } + else if (channel == 3) + { + writeRegisterMasked(REG_CH3_GCAL_MSB, MSB, 0xFFFF); + writeRegisterMasked(REG_CH3_GCAL_LSB, (uint16_t)LSB << 8, REGMASK_CHX_GCAL0_LSB); + return true; + } +} + +bool ADS131M04::isDataReady() +{ + if (digitalRead(ADS131M04_DRDY_PIN) == HIGH) + { + return false; + } + return true; +} + +bool ADS131M04::readADC(adcOutput* reading) +{ + uint32_t x = 0; + uint32_t x2 = 0; + uint32_t x3 = 0; + uint8_t crc_inputs[15]; + uint16_t crc_received = 0; + int32_t aux; + + SPI.beginTransaction(SPI_SETTINGS); + digitalWrite(ADS131M04_CS_PIN, LOW); + delayMicroseconds(1); + + x = crc_inputs[0] = SPI.transfer(0x00); + x2 = crc_inputs[1] = SPI.transfer(0x00); + crc_inputs[2] = SPI.transfer(0x00); + + reading->status = ((x << 8) | x2); + + x = crc_inputs[3] = SPI.transfer(0x00); + x2 = crc_inputs[4] = SPI.transfer(0x00); + x3 = crc_inputs[5] = SPI.transfer(0x00); + + aux = (((x << 16) | (x2 << 8) | x3) & 0x00FFFFFF); + if (aux > 0x7FFFFF) + { + reading->ch0 = ((~(aux)&0x00FFFFFF) + 1) * -1; + } + else + { + reading->ch0 = aux; + } + + x = crc_inputs[6] = SPI.transfer(0x00); + x2 = crc_inputs[7] = SPI.transfer(0x00); + x3 = crc_inputs[8] = SPI.transfer(0x00); + + aux = (((x << 16) | (x2 << 8) | x3) & 0x00FFFFFF); + if (aux > 0x7FFFFF) + { + reading->ch1 = ((~(aux)&0x00FFFFFF) + 1) * -1; + } + else + { + reading->ch1 = aux; + } + + x = crc_inputs[9] = SPI.transfer(0x00); + x2 = crc_inputs[10] = SPI.transfer(0x00); + x3 = crc_inputs[11] = SPI.transfer(0x00); + + aux = (((x << 16) | (x2 << 8) | x3) & 0x00FFFFFF); + if (aux > 0x7FFFFF) + { + reading->ch2 = ((~(aux)&0x00FFFFFF) + 1) * -1; + } + else + { + reading->ch2 = aux; + } + + x = crc_inputs[12] = SPI.transfer(0x00); + x2 = crc_inputs[13] = SPI.transfer(0x00); + x3 = crc_inputs[14] = SPI.transfer(0x00); + + aux = (((x << 16) | (x2 << 8) | x3) & 0x00FFFFFF); + if (aux > 0x7FFFFF) + { + reading->ch3 = ((~(aux)&0x00FFFFFF) + 1) * -1; + } + else + { + reading->ch3 = aux; + } + + x = SPI.transfer(0x00); + x2 = SPI.transfer(0x00); + SPI.transfer(0x00); + crc_received = ((x << 8) | x2); + + delayMicroseconds(1); + digitalWrite(ADS131M04_CS_PIN, HIGH); + SPI.endTransaction(); + + if (crc_received != crc16_CCITT(crc_inputs, 15)) + { + // CRC error (SPI communication error) + return false; + } + + return true; +} diff --git a/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/ADS131M04.h b/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/ADS131M04.h new file mode 100644 index 000000000..8584c48b8 --- /dev/null +++ b/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/ADS131M04.h @@ -0,0 +1,274 @@ +/* + Based on https://github.com/LucasEtchezuri/Arduino-ADS131M04/blob/ed879ecd0da5ac0d1dfa0494ff65b1e7ad4ae6d5/ADS131M04.h + See ADS131M04.cpp for detailed comments about modification. +*/ + +#ifndef ADS131M04_h +#define ADS131M04_h + +#include "Arduino.h" +#include "SPI.h" + +struct adcOutput +{ + uint16_t status; + int32_t ch0; + int32_t ch1; + int32_t ch2; + int32_t ch3; +}; + +#define DRDY_STATE_LOGIC_HIGH 0 // Default +#define DRDY_STATE_HI_Z 1 + +#define POWER_MODE_VERY_LOW_POWER 0 +#define POWER_MODE_LOW_POWER 1 +#define POWER_MODE_HIGH_RESOLUTION 2 // Default + +#define CHANNEL_PGA_1 0 +#define CHANNEL_PGA_2 1 +#define CHANNEL_PGA_4 2 +#define CHANNEL_PGA_8 3 +#define CHANNEL_PGA_16 4 +#define CHANNEL_PGA_32 5 +#define CHANNEL_PGA_64 6 +#define CHANNEL_PGA_128 7 + +#define INPUT_CHANNEL_MUX_AIN0P_AIN0N 0 // Default +#define INPUT_CHANNEL_MUX_INPUT_SHORTED 1 +#define INPUT_CHANNEL_MUX_POSITIVE_DC_TEST_SIGNAL 2 +#define INPUT_CHANNEL_MUX_NEGATIVE_DC_TEST_SIGNAL 3 + +#define OSR_128 0 +#define OSR_256 1 +#define OSR_512 2 +#define OSR_1024 3 // Default +#define OSR_2018 4 +#define OSR_4096 5 +#define OSR_8192 6 +#define OSR_16384 7 + + +// Commands +#define CMD_NULL 0x0000 // This command gives the STATUS REGISTER +#define CMD_RESET 0x0011 +#define CMD_STANDBY 0x0022 +#define CMD_WAKEUP 0x0033 +#define CMD_LOCK 0x0555 +#define CMD_UNLOCK 0x0655 +#define CMD_READ_REG 0xA000 // 101a aaaa annn nnnn a=address n=number of registers-1 +#define CMD_WRITE_REG 0x6000 + +// Responses +#define RSP_RESET_OK 0xFF24 +#define RSP_RESET_NOK 0x0011 + +// Registers Read Only +#define REG_ID 0x00 +#define REG_STATUS 0x01 + +// Registers Global Settings across channels +#define REG_MODE 0x02 +#define REG_CLOCK 0x03 +#define REG_GAIN 0x04 +#define REG_CFG 0x06 +#define THRSHLD_MSB 0x07 +#define THRSHLD_LSB 0x08 + +// Registers Channel 0 Specific +#define REG_CH0_CFG 0x09 +#define REG_CH0_OCAL_MSB 0x0A +#define REG_CH0_OCAL_LSB 0x0B +#define REG_CH0_GCAL_MSB 0x0C +#define REG_CH0_GCAL_LSB 0x0D + +// Registers Channel 1 Specific +#define REG_CH1_CFG 0x0E +#define REG_CH1_OCAL_MSB 0x0F +#define REG_CH1_OCAL_LSB 0x10 +#define REG_CH1_GCAL_MSB 0x11 +#define REG_CH1_GCAL_LSB 0x12 + +// Registers Channel 2 Specific +#define REG_CH2_CFG 0x13 +#define REG_CH2_OCAL_MSB 0x14 +#define REG_CH2_OCAL_LSB 0x15 +#define REG_CH2_GCAL_MSB 0x16 +#define REG_CH2_GCAL_LSB 0x17 + +// Registers Channel 3 Specific +#define REG_CH3_CFG 0x18 +#define REG_CH3_OCAL_MSB 0x19 +#define REG_CH3_OCAL_LSB 0x1A +#define REG_CH3_GCAL_MSB 0x1B +#define REG_CH3_GCAL_LSB 0x1C + +// Registers MAP CRC +#define REG_MAP_CRC 0x3E + +// ------------------------------------------------------------------------------------ + +// Mask READ_REG +#define REGMASK_CMD_READ_REG_ADDRESS 0x1F80 +#define REGMASK_CMD_READ_REG_BYTES 0x007F + +// Mask Register STATUS +#define REGMASK_STATUS_LOCK 0x8000 +#define REGMASK_STATUS_RESYNC 0x4000 +#define REGMASK_STATUS_REGMAP 0x2000 +#define REGMASK_STATUS_CRC_ERR 0x1000 +#define REGMASK_STATUS_CRC_TYPE 0x0800 +#define REGMASK_STATUS_RESET 0x0400 +#define REGMASK_STATUS_WLENGTH 0x0300 +#define REGMASK_STATUS_DRDY3 0x0008 +#define REGMASK_STATUS_DRDY2 0x0004 +#define REGMASK_STATUS_DRDY1 0x0002 +#define REGMASK_STATUS_DRDY0 0x0001 + +// Mask Register MODE +#define REGMASK_MODE_REG_CRC_EN 0x2000 +#define REGMASK_MODE_RX_CRC_EN 0x1000 +#define REGMASK_MODE_CRC_TYPE 0x0800 +#define REGMASK_MODE_RESET 0x0400 +#define REGMASK_MODE_WLENGTH 0x0300 +#define REGMASK_MODE_TIMEOUT 0x0010 +#define REGMASK_MODE_DRDY_SEL 0x000C +#define REGMASK_MODE_DRDY_HiZ 0x0002 +#define REGMASK_MODE_DRDY_FMT 0x0001 + +// Mask Register CLOCK +#define REGMASK_CLOCK_CH3_EN 0x0800 +#define REGMASK_CLOCK_CH2_EN 0x0400 +#define REGMASK_CLOCK_CH1_EN 0x0200 +#define REGMASK_CLOCK_CH0_EN 0x0100 +#define REGMASK_CLOCK_OSR 0x001C +#define REGMASK_CLOCK_PWR 0x0003 + +// Mask Register GAIN +#define REGMASK_GAIN_PGAGAIN3 0x7000 +#define REGMASK_GAIN_PGAGAIN2 0x0700 +#define REGMASK_GAIN_PGAGAIN1 0x0070 +#define REGMASK_GAIN_PGAGAIN0 0x0007 + +// Mask Register CFG +#define REGMASK_CFG_GC_DLY 0x1E00 +#define REGMASK_CFG_GC_EN 0x0100 +#define REGMASK_CFG_CD_ALLCH 0x0080 +#define REGMASK_CFG_CD_NUM 0x0070 +#define REGMASK_CFG_CD_LEN 0x000E +#define REGMASK_CFG_CD_EN 0x0001 + +// Mask Register THRSHLD_LSB +#define REGMASK_THRSHLD_LSB_CD_TH_LSB 0xFF00 +#define REGMASK_THRSHLD_LSB_DCBLOCK 0x000F + +// Mask Register CHX_CFG +#define REGMASK_CHX_CFG_PHASE 0xFFC0 +#define REGMASK_CHX_CFG_DCBLKX_DIS0 0x0004 +#define REGMASK_CHX_CFG_MUX 0x0003 + +// Mask Register CHX_OCAL_LSB +#define REGMASK_CHX_OCAL0_LSB 0xFF00 + +// Mask Register CHX_GCAL_LSB +#define REGMASK_CHX_GCAL0_LSB 0xFF00 + +// -------------------------------------------------------------------- + +// Conversion modes +#define CONVERSION_MODE_CONT 0 +#define CONVERSION_MODE_SINGLE 1 + +// Data Format +#define DATA_FORMAT_TWO_COMPLEMENT 0 +#define DATA_FORMAT_BINARY 1 + +// Measure Mode +#define MEASURE_UNIPOLAR 1 +#define MEASURE_BIPOLAR 0 + +// Clock Type +#define CLOCK_EXTERNAL 1 +#define CLOCK_INTERNAL 0 + +// PGA Gain +#define PGA_GAIN_1 0 +#define PGA_GAIN_2 1 +#define PGA_GAIN_4 2 +#define PGA_GAIN_8 3 +#define PGA_GAIN_16 4 +#define PGA_GAIN_32 5 +#define PGA_GAIN_64 6 +#define PGA_GAIN_128 7 + +// Input Filter +#define FILTER_SINC 0 +#define FILTER_FIR 2 +#define FILTER_FIR_IIR 3 + +// Data Mode +#define DATA_MODE_24BITS 0 +#define DATA_MODE_32BITS 1 + +// Data Rate +#define DATA_RATE_0 0 +#define DATA_RATE_1 1 +#define DATA_RATE_2 2 +#define DATA_RATE_3 3 +#define DATA_RATE_4 4 +#define DATA_RATE_5 5 +#define DATA_RATE_6 6 +#define DATA_RATE_7 7 +#define DATA_RATE_8 8 +#define DATA_RATE_9 9 +#define DATA_RATE_10 10 +#define DATA_RATE_11 11 +#define DATA_RATE_12 12 +#define DATA_RATE_13 13 +#define DATA_RATE_14 14 +#define DATA_RATE_15 15 + +// Sync Mode +#define SYNC_CONTINUOUS 1 +#define SYNC_PULSE 0 + +// DIO Config Mode +#define DIO_OUTPUT 1 +#define DIO_INPUT 0 + +#define SPI_MASTER_DUMMY 0xFF +#define SPI_MASTER_DUMMY16 0xFFFF +#define SPI_MASTER_DUMMY32 0xFFFFFFFF + +class ADS131M04 +{ +public: + ADS131M04(); + uint8_t ADS131M04_CS_PIN; + uint8_t ADS131M04_DRDY_PIN; + SPISettings SPI_SETTINGS; + + void begin(uint8_t cs_pin, uint8_t drdy_pin, uint32_t spi_speed = 4000000); + int8_t isDataReadySoft(byte channel); + bool isDataReady(void); + bool isResetStatus(void); + bool isLockSPI(void); + bool setDrdyFormat(uint8_t drdyFormat); + bool setDrdyStateWhenUnavailable(uint8_t drdyState); + bool setPowerMode(uint8_t powerMode); + bool setChannelEnable(uint8_t channel, uint16_t enable); + bool setChannelPGA(uint8_t channel, uint16_t pga); + void setGlobalChop(uint16_t global_chop); + void setGlobalChopDelay(uint16_t delay); + bool setInputChannelSelection(uint8_t channel, uint8_t input); + bool setChannelOffsetCalibration(uint8_t channel, int32_t offset); + bool setChannelGainCalibration(uint8_t channel, uint32_t gain); + bool setOsr(uint16_t osr); + bool readADC(adcOutput* reading); + +private: + uint8_t writeRegister(uint8_t address, uint16_t value); + void writeRegisterMasked(uint8_t address, uint16_t value, uint16_t mask); + uint16_t readRegister(uint8_t address); +}; +#endif diff --git a/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/sensor_driver_raw_rmd_ads131m04.ino b/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/sensor_driver_raw_rmd_ads131m04.ino new file mode 100644 index 000000000..48454d301 --- /dev/null +++ b/bl_force_torque_sensor_ros/arduino/sensor_driver_raw_rmd_ads131m04/sensor_driver_raw_rmd_ads131m04.ino @@ -0,0 +1,109 @@ +#include + +#include +#include +#include + +#include "ADS131M04.h" + +#define ADC_NUM 2 +#define VALID_CH_NUM 3 +ADS131M04 adc[ADC_NUM]; +adcOutput adc_res[ADC_NUM]; + +unsigned long loop_duration = 0; // loop duration in us, set via rostopic callback +unsigned long start_time; + +ros::NodeHandle nh; +bl_force_torque_sensor_ros::AmplifierOutputRawArray output_msg; +ros::Publisher output_pub("~output", &output_msg); +int32_t output_array[ADC_NUM * VALID_CH_NUM]; + +void durationCb(const std_msgs::Float32& duration_msg) +{ + loop_duration = duration_msg.data * 1000 * 1000; // sec -> us +} + +ros::Subscriber duration_sub("~parameter_getter/loop_duration", &durationCb); + +void setup() +{ + nh.getHardware()->setBaud(250000); + // cf. https://arduino.stackexchange.com/questions/296/how-high-of-a-baud-rate-can-i-go-without-errors + // http://wormfood.net/avrbaudcalc.php + // I'm not sure why, but 500000 causes "Mismatched protocol version" error + nh.initNode(); + nh.advertise(output_pub); + nh.subscribe(duration_sub); + while (!nh.connected()) + { + nh.spinOnce(); + } + + adc[0].begin(9, 7, 4000000); + adc[1].begin(8, 6, 4000000); // I'm not sure why, but using D10 as CS doesn't work. Conflicted with SPI.begin()? +} + +void loop() +{ + start_time = micros(); + + // Wait until data is ready + bool is_ok = false; + while (!is_ok) + { + is_ok = true; + for (int i = 0; i < ADC_NUM; i++) + { + if (!adc[i].isDataReady()) + { + is_ok = false; + } + } + } + + is_ok = true; + for (int i = 0; i < ADC_NUM; i++) + { + if (!adc[i].readADC(&adc_res[i])) + { + is_ok = false; + } + delayMicroseconds(1); // Should be longer than t_w(CSH)? + } + for (int i = 0; i < ADC_NUM; i++) + { + if (adc_res[i].status != 0x050F) + { + // status is value of STATUS register. + // By default, if everything is OK, this value is 0000 0101 0000 1111 (0x050F) + is_ok = false; + } + } + if (is_ok) + { + for (int i = 0; i < ADC_NUM; i++) + { + output_array[i * VALID_CH_NUM] = adc_res[i].ch0; + output_array[i * VALID_CH_NUM + 1] = adc_res[i].ch1; + output_array[i * VALID_CH_NUM + 2] = adc_res[i].ch2; + } + output_msg.array = output_array; + output_msg.array_length = ADC_NUM * VALID_CH_NUM; + output_pub.publish(&output_msg); + } + + nh.spinOnce(); + // Enforce constant loop time + if ((ULONG_MAX - start_time) < loop_duration) + { + // micros() will overflow while waiting + while (micros() >= start_time || micros() < (loop_duration - (ULONG_MAX - start_time))); + } + else if (micros() < start_time); // micros() already overflowed unexpectedly + else + { + // Normal waiting + while ((micros() - start_time) < loop_duration); + } +} diff --git a/bl_force_torque_sensor_ros/circuits/rmd_ads131m04_arduino/schematic.jpg b/bl_force_torque_sensor_ros/circuits/rmd_ads131m04_arduino/schematic.jpg new file mode 100644 index 000000000..420c7cc8e Binary files /dev/null and b/bl_force_torque_sensor_ros/circuits/rmd_ads131m04_arduino/schematic.jpg differ diff --git a/bl_force_torque_sensor_ros/config/rviz/sample.rviz b/bl_force_torque_sensor_ros/config/rviz/sample.rviz new file mode 100644 index 000000000..777d94b63 --- /dev/null +++ b/bl_force_torque_sensor_ros/config/rviz/sample.rviz @@ -0,0 +1,134 @@ +Panels: + - Class: rviz/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + Splitter Ratio: 0.5 + Tree Height: 549 + - Class: rviz/Selection + Name: Selection + - Class: rviz/Tool Properties + Expanded: + - /2D Pose Estimate1 + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 + - Class: rviz/Time + Experimental: false + Name: Time + SyncMode: 0 + SyncSource: "" +Preferences: + PromptSaveOnExit: true +Toolbars: + toolButtonStyle: 2 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Alpha: 1 + Arrow Width: 0.5 + Class: rviz/WrenchStamped + Enabled: true + Force Arrow Scale: 2 + Force Color: 204; 51; 51 + Hide Small Values: true + History Length: 1 + Name: WrenchStamped + Topic: /bl_force_torque_sensor_driver/output/wrench + Torque Arrow Scale: 20 + Torque Color: 204; 204; 51 + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Default Light: true + Fixed Frame: bl_force_torque_sensor + Frame Rate: 30 + Name: root + Tools: + - Class: rviz/Interact + Hide Inactive Objects: true + - Class: rviz/MoveCamera + - Class: rviz/Select + - Class: rviz/FocusCamera + - Class: rviz/Measure + - Class: rviz/SetInitialPose + Theta std deviation: 0.2617993950843811 + Topic: /initialpose + X std deviation: 0.5 + Y std deviation: 0.5 + - Class: rviz/SetGoal + Topic: /move_base_simple/goal + - Class: rviz/PublishPoint + Single click: true + Topic: /clicked_point + Value: true + Views: + Current: + Class: rviz/Orbit + Distance: 10 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.785398006439209 + Target Frame: + Value: Orbit (rviz) + Yaw: 0.785398006439209 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 846 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd000000040000000000000156000002b0fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000002b0000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002b0fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d000002b0000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004f30000003efc0100000002fb0000000800540069006d00650100000000000004f3000002eb00fffffffb0000000800540069006d0065010000000000000450000000000000000000000282000002b000000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Time: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1267 + X: 60 + Y: 27 diff --git a/bl_force_torque_sensor_ros/launch/sample.launch b/bl_force_torque_sensor_ros/launch/sample.launch new file mode 100644 index 000000000..657bf6fc0 --- /dev/null +++ b/bl_force_torque_sensor_ros/launch/sample.launch @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bl_force_torque_sensor_ros/launch/sensor_driver_rosserial.launch b/bl_force_torque_sensor_ros/launch/sensor_driver_rosserial.launch new file mode 100644 index 000000000..185662358 --- /dev/null +++ b/bl_force_torque_sensor_ros/launch/sensor_driver_rosserial.launch @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + port: $(arg port) + baud: $(arg baud) + + + + + + + + calib_matrix: $(arg calib_matrix) + sensor_type: $(arg sensor_type) + amplifier_type: $(arg amplifier_type) + adc_type: $(arg adc_type) + sensor_frame_id: $(arg sensor_frame_id) + rate: $(arg rate) + + + + diff --git a/bl_force_torque_sensor_ros/msg/AmplifierOutputRawArray.msg b/bl_force_torque_sensor_ros/msg/AmplifierOutputRawArray.msg new file mode 100644 index 000000000..a23efbbb5 --- /dev/null +++ b/bl_force_torque_sensor_ros/msg/AmplifierOutputRawArray.msg @@ -0,0 +1,5 @@ +# Output voltage from amplifiers of strain gauges. +# This msg targets communication using rosserial. +# To lighten process on microprocessor, this msg is intended to contain original A/D converter counts. + +int32[] array # Output voltage from amplifiers of strain gauges in A/D converter counts diff --git a/bl_force_torque_sensor_ros/node_scripts/sensor_driver_rosserial.py b/bl_force_torque_sensor_ros/node_scripts/sensor_driver_rosserial.py new file mode 100755 index 000000000..153b33e34 --- /dev/null +++ b/bl_force_torque_sensor_ros/node_scripts/sensor_driver_rosserial.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python + +import numpy as np + +import rospy +from bl_force_torque_sensor_ros.msg import AmplifierOutputRawArray +from geometry_msgs.msg import WrenchStamped +from std_msgs.msg import Float32 + + +class SensorDriverRosserial(object): + + def __init__(self): + self.calib_matrix = rospy.get_param('~calib_matrix', None) + if self.calib_matrix is None: + rospy.logfatal( + 'Please specify calibration matrix of your sensor') + return + self.calib_matrix = np.array(self.calib_matrix) + + self.sensor_type = rospy.get_param( + '~sensor_type', 'NANO2.5/2-A') + if self.sensor_type == 'NANO1.2/1-A': + self.n_per_count = 4.9e-3 # N/count + self.nm_per_count = 4.9e-5 # Nm/count + elif self.sensor_type == 'NANO2.5/2-A': + self.n_per_count = 9.8e-3 + self.nm_per_count = 9.8e-5 + elif self.sensor_type == 'NANO5/4-A': + self.n_per_count = 24.5e-3 + self.nm_per_count = 19.6e-5 + else: + rospy.logfatal( + 'Unimplemented sensor type: %s', self.sensor_type) + return + + self.amplifier_type = rospy.get_param( + '~amplifier_type', 'RMD') + if self.amplifier_type == 'RMD' or self.amplifier_type == 'RMS': + self.amp_effective_range_max = 5.0 + elif self.amplifier_type == 'RMSH': + self.amp_effective_range_max = 2.5 + else: + rospy.logfatal( + 'Unimplemented amplifier type: %s', self.amplifier_type) + return + + self.adc_type = rospy.get_param( + '~adc_type', 'ADS131M04') + if self.adc_type == 'ADS131M04': + self.voltage_divider = (10.0 + 1.1) / 1.1 + self.adc_range = 2.4 # From -FSR to +FSR + self.adc_resolution = 24 # Number of bits + else: + rospy.logfatal( + 'Unimplemented ADC type: %s', self.adc_type) + return + + self.sensor_frame_id = rospy.get_param( + '~sensor_frame_id', 'bl_force_torque_sensor') + + self.rate = rospy.get_param('~rate', -1) # Hz + if self.rate <= 0: + self.loop_duration = 0 + else: + self.loop_duration = 1.0 / self.rate # sec + self.duration_pub = rospy.Publisher( + '~parameter_setter/loop_duration', + Float32, + queue_size=1, + latch=True + ) + self.duration_pub.publish(Float32(self.loop_duration)) + + self.wrench_pub = rospy.Publisher( + '~output/wrench', WrenchStamped, queue_size=1) + self.input_sub = rospy.Subscriber( + '~input', AmplifierOutputRawArray, self.callback) + + def callback(self, in_msg): + stamp = rospy.Time.now() # Get timestamp ASAP after message arrival + + # Convert ADC count of amplifier output to force/torque + v_raw = np.array(in_msg.array) + ## Based on ADS131M04 datasheet Revision C p. 38 + v_volt = (v_raw * + (float(self.adc_range) / (2 ** self.adc_resolution)) * + self.voltage_divider) + ## Based on NANO sensor manual Rev3.1 p. 8 + v = 2048 * v_volt / self.amp_effective_range_max + ## Based on NANO sensor manual Rev3.1 p. 9 + f = np.dot(self.calib_matrix, v) + real_force = f[:3] * self.n_per_count + real_torque = f[3:] * self.nm_per_count + + # Publish force/torque as WrenchStamped + wrench_msg = WrenchStamped() + wrench_msg.header.stamp = stamp + wrench_msg.header.frame_id = self.sensor_frame_id + wrench_msg.wrench.force.x = real_force[0] + wrench_msg.wrench.force.y = real_force[1] + wrench_msg.wrench.force.z = real_force[2] + wrench_msg.wrench.torque.x = real_torque[0] + wrench_msg.wrench.torque.y = real_torque[1] + wrench_msg.wrench.torque.z = real_torque[2] + self.wrench_pub.publish(wrench_msg) + + +if __name__ == '__main__': + rospy.init_node('bl_force_torque_sensor_driver') + app = SensorDriverRosserial() + rospy.spin() diff --git a/bl_force_torque_sensor_ros/package.xml b/bl_force_torque_sensor_ros/package.xml new file mode 100644 index 000000000..413ace3fa --- /dev/null +++ b/bl_force_torque_sensor_ros/package.xml @@ -0,0 +1,25 @@ + + + bl_force_torque_sensor_ros + 2.1.14 + ROS driver package for BL force torque sensor + + Shun Hasegawa + Kei Okada + Shun Hasegawa + + BSD + + catkin + message_generation + message_runtime + message_runtime + rospy + rosserial_arduino + rosserial_python + std_msgs + geometry_msgs + + + +