001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package org.wpilib.hardware.accelerometer; 006 007import java.nio.ByteBuffer; 008import java.nio.ByteOrder; 009import org.wpilib.hardware.bus.I2C; 010import org.wpilib.hardware.hal.HAL; 011import org.wpilib.hardware.hal.SimDevice; 012import org.wpilib.hardware.hal.SimDouble; 013import org.wpilib.hardware.hal.SimEnum; 014import org.wpilib.networktables.DoublePublisher; 015import org.wpilib.networktables.DoubleTopic; 016import org.wpilib.networktables.NTSendable; 017import org.wpilib.networktables.NTSendableBuilder; 018import org.wpilib.util.sendable.SendableRegistry; 019 020/** ADXL345 I2C Accelerometer. */ 021@SuppressWarnings("TypeName") 022public class ADXL345_I2C implements NTSendable, AutoCloseable { 023 /** Default I2C device address. */ 024 public static final byte kAddress = 0x1D; 025 026 private static final byte kPowerCtlRegister = 0x2D; 027 private static final byte kDataFormatRegister = 0x31; 028 private static final byte kDataRegister = 0x32; 029 private static final double kGsPerLSB = 0.00390625; 030 // private static final byte kPowerCtl_Link = 0x20; 031 // private static final byte kPowerCtl_AutoSleep = 0x10; 032 private static final byte kPowerCtl_Measure = 0x08; 033 // private static final byte kPowerCtl_Sleep = 0x04; 034 035 // private static final byte kDataFormat_SelfTest = (byte) 0x80; 036 // private static final byte kDataFormat_SPI = 0x40; 037 // private static final byte kDataFormat_IntInvert = 0x20; 038 private static final byte kDataFormat_FullRes = 0x08; 039 040 // private static final byte kDataFormat_Justify = 0x04; 041 042 /** Accelerometer range. */ 043 public enum Range { 044 /** 2 Gs max. */ 045 k2G, 046 /** 4 Gs max. */ 047 k4G, 048 /** 8 Gs max. */ 049 k8G, 050 /** 16 Gs max. */ 051 k16G 052 } 053 054 /** Accelerometer axes. */ 055 public enum Axes { 056 /** X axis. */ 057 kX((byte) 0x00), 058 /** Y axis. */ 059 kY((byte) 0x02), 060 /** Z axis. */ 061 kZ((byte) 0x04); 062 063 /** The integer value representing this enumeration. */ 064 public final byte value; 065 066 Axes(byte value) { 067 this.value = value; 068 } 069 } 070 071 /** Container type for accelerations from all axes. */ 072 @SuppressWarnings("MemberName") 073 public static class AllAxes { 074 /** Acceleration along the X axis in g-forces. */ 075 public double XAxis; 076 077 /** Acceleration along the Y axis in g-forces. */ 078 public double YAxis; 079 080 /** Acceleration along the Z axis in g-forces. */ 081 public double ZAxis; 082 083 /** Default constructor. */ 084 public AllAxes() {} 085 } 086 087 private I2C m_i2c; 088 089 private SimDevice m_simDevice; 090 private SimEnum m_simRange; 091 private SimDouble m_simX; 092 private SimDouble m_simY; 093 private SimDouble m_simZ; 094 095 /** 096 * Constructs the ADXL345 Accelerometer with I2C address 0x1D. 097 * 098 * @param port The I2C port the accelerometer is attached to 099 * @param range The range (+ or -) that the accelerometer will measure. 100 */ 101 public ADXL345_I2C(I2C.Port port, Range range) { 102 this(port, range, kAddress); 103 } 104 105 /** 106 * Constructs the ADXL345 Accelerometer over I2C. 107 * 108 * @param port The I2C port the accelerometer is attached to 109 * @param range The range (+ or -) that the accelerometer will measure. 110 * @param deviceAddress I2C address of the accelerometer (0x1D or 0x53) 111 */ 112 @SuppressWarnings("this-escape") 113 public ADXL345_I2C(I2C.Port port, Range range, int deviceAddress) { 114 m_i2c = new I2C(port, deviceAddress); 115 116 // simulation 117 m_simDevice = SimDevice.create("Accel:ADXL345_I2C", port.value, deviceAddress); 118 if (m_simDevice != null) { 119 m_simRange = 120 m_simDevice.createEnumDouble( 121 "range", 122 SimDevice.Direction.kOutput, 123 new String[] {"2G", "4G", "8G", "16G"}, 124 new double[] {2.0, 4.0, 8.0, 16.0}, 125 0); 126 m_simX = m_simDevice.createDouble("x", SimDevice.Direction.kInput, 0.0); 127 m_simY = m_simDevice.createDouble("y", SimDevice.Direction.kInput, 0.0); 128 m_simZ = m_simDevice.createDouble("z", SimDevice.Direction.kInput, 0.0); 129 } 130 131 // Turn on the measurements 132 m_i2c.write(kPowerCtlRegister, kPowerCtl_Measure); 133 134 setRange(range); 135 136 HAL.reportUsage("I2C[" + port.value + "][" + deviceAddress + "]", "ADXL345"); 137 SendableRegistry.add(this, "ADXL345_I2C", port.value); 138 } 139 140 /** 141 * Returns the I2C port. 142 * 143 * @return The I2C port. 144 */ 145 public int getPort() { 146 return m_i2c.getPort(); 147 } 148 149 /** 150 * Returns the I2C device address. 151 * 152 * @return The I2C device address. 153 */ 154 public int getDeviceAddress() { 155 return m_i2c.getDeviceAddress(); 156 } 157 158 @Override 159 public void close() { 160 SendableRegistry.remove(this); 161 if (m_i2c != null) { 162 m_i2c.close(); 163 m_i2c = null; 164 } 165 if (m_simDevice != null) { 166 m_simDevice.close(); 167 m_simDevice = null; 168 } 169 } 170 171 /** 172 * Set the measuring range of the accelerometer. 173 * 174 * @param range The maximum acceleration, positive or negative, that the accelerometer will 175 * measure. 176 */ 177 public final void setRange(Range range) { 178 final byte value = 179 switch (range) { 180 case k2G -> 0; 181 case k4G -> 1; 182 case k8G -> 2; 183 case k16G -> 3; 184 }; 185 186 // Specify the data format to read 187 m_i2c.write(kDataFormatRegister, kDataFormat_FullRes | value); 188 189 if (m_simRange != null) { 190 m_simRange.set(value); 191 } 192 } 193 194 /** 195 * Returns the acceleration along the X axis in g-forces. 196 * 197 * @return The acceleration along the X axis in g-forces. 198 */ 199 public double getX() { 200 return getAcceleration(Axes.kX); 201 } 202 203 /** 204 * Returns the acceleration along the Y axis in g-forces. 205 * 206 * @return The acceleration along the Y axis in g-forces. 207 */ 208 public double getY() { 209 return getAcceleration(Axes.kY); 210 } 211 212 /** 213 * Returns the acceleration along the Z axis in g-forces. 214 * 215 * @return The acceleration along the Z axis in g-forces. 216 */ 217 public double getZ() { 218 return getAcceleration(Axes.kZ); 219 } 220 221 /** 222 * Get the acceleration of one axis in Gs. 223 * 224 * @param axis The axis to read from. 225 * @return Acceleration of the ADXL345 in Gs. 226 */ 227 public double getAcceleration(Axes axis) { 228 if (axis == Axes.kX && m_simX != null) { 229 return m_simX.get(); 230 } 231 if (axis == Axes.kY && m_simY != null) { 232 return m_simY.get(); 233 } 234 if (axis == Axes.kZ && m_simZ != null) { 235 return m_simZ.get(); 236 } 237 ByteBuffer rawAccel = ByteBuffer.allocate(2); 238 m_i2c.read(kDataRegister + axis.value, 2, rawAccel); 239 240 // Sensor is little endian... swap bytes 241 rawAccel.order(ByteOrder.LITTLE_ENDIAN); 242 return rawAccel.getShort(0) * kGsPerLSB; 243 } 244 245 /** 246 * Get the acceleration of all axes in Gs. 247 * 248 * @return An object containing the acceleration measured on each axis of the ADXL345 in Gs. 249 */ 250 public AllAxes getAccelerations() { 251 AllAxes data = new AllAxes(); 252 if (m_simX != null && m_simY != null && m_simZ != null) { 253 data.XAxis = m_simX.get(); 254 data.YAxis = m_simY.get(); 255 data.ZAxis = m_simZ.get(); 256 return data; 257 } 258 ByteBuffer rawData = ByteBuffer.allocate(6); 259 m_i2c.read(kDataRegister, 6, rawData); 260 261 // Sensor is little endian... swap bytes 262 rawData.order(ByteOrder.LITTLE_ENDIAN); 263 data.XAxis = rawData.getShort(0) * kGsPerLSB; 264 data.YAxis = rawData.getShort(2) * kGsPerLSB; 265 data.ZAxis = rawData.getShort(4) * kGsPerLSB; 266 return data; 267 } 268 269 @Override 270 public void initSendable(NTSendableBuilder builder) { 271 builder.setSmartDashboardType("3AxisAccelerometer"); 272 DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish(); 273 DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish(); 274 DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish(); 275 builder.addCloseable(pubX); 276 builder.addCloseable(pubY); 277 builder.addCloseable(pubZ); 278 builder.setUpdateTable( 279 () -> { 280 AllAxes data = getAccelerations(); 281 pubX.set(data.XAxis); 282 pubY.set(data.YAxis); 283 pubZ.set(data.ZAxis); 284 }); 285 } 286}