LSM9DS1 the other library
Fork of LSM9DS1 by
Revision 8:16e88babd42a, committed 2017-06-19
- Comitter:
- benson516
- Date:
- Mon Jun 19 02:25:48 2017 +0000
- Parent:
- 7:e6e3d320eb6c
- Commit message:
- Fix bugs in setting resolutions
Changed in this revision
diff -r e6e3d320eb6c -r 16e88babd42a LSM9DS1.cpp --- a/LSM9DS1.cpp Thu Jun 15 08:42:23 2017 +0000 +++ b/LSM9DS1.cpp Mon Jun 19 02:25:48 2017 +0000 @@ -7,7 +7,7 @@ mAddress = mAddr; } -uint16_t LSM9DS1::begin(gyro_scale gScl, accel_scale aScl, mag_scale mScl, +bool LSM9DS1::begin(gyro_scale gScl, accel_scale aScl, mag_scale mScl, gyro_odr gODR, accel_odr aODR, mag_odr mODR) { // Store the given scales in class variables. These scale variables @@ -15,14 +15,14 @@ gScale = gScl; aScale = aScl; mScale = mScl; - + // Once we have the scale values, we can calculate the resolution // of each sensor. That's what these functions are for. One for each sensor calcgRes(); // Calculate DPS / ADC tick, stored in gRes variable calcmRes(); // Calculate Gs / ADC tick, stored in mRes variable calcaRes(); // Calculate g / ADC tick, stored in aRes variable - - + + // To verify communication, we can read from the WHO_AM_I register of // each device. Store those in a variable so we can return them. // The start of the addresses we want to read from @@ -36,7 +36,7 @@ // Read in all the 8 bits of data i2c.read(xgAddress, cmd+1, 1); uint8_t xgTest = cmd[1]; // Read the accel/gyro WHO_AM_I - + // Reset to the address of the mag who am i cmd[0] = WHO_AM_I_M; // Write the address we are going to read from and don't end the transaction @@ -44,46 +44,55 @@ // Read in all the 8 bits of data i2c.read(mAddress, cmd+1, 1); uint8_t mTest = cmd[1]; // Read the mag WHO_AM_I - + for(int ii = 0; ii < 3; ii++) { gBiasRaw[ii] = 0; aBiasRaw[ii] = 0; gBias[ii] = 0; aBias[ii] = 0; - autoCalib = false; } - - // Gyro initialization stuff: - initGyro(); // This will "turn on" the gyro. Setting up interrupts, etc. - setGyroODR(gODR); // Set the gyro output data rate and bandwidth. - setGyroScale(gScale); // Set the gyro range - + autoCalib = false; + // Accelerometer initialization stuff: initAccel(); // "Turn on" all axes of the accel. Set up interrupts, etc. setAccelODR(aODR); // Set the accel data rate. setAccelScale(aScale); // Set the accel range. - + + // Gyro initialization stuff: + initGyro(); // This will "turn on" the gyro. Setting up interrupts, etc. + setGyroODR(gODR); // Set the gyro output data rate and bandwidth. + setGyroScale(gScale); // Set the gyro range + // Magnetometer initialization stuff: initMag(); // "Turn on" all axes of the mag. Set up interrupts, etc. setMagODR(mODR); // Set the magnetometer output data rate. setMagScale(mScale); // Set the magnetometer's range. - + // Interrupt initialization stuff initIntr(); - + // Once everything is initialized, return the WHO_AM_I registers we read: - return (xgTest << 8) | mTest; + return ( ((xgTest << 8) | mTest) == (WHO_AM_I_AG_RSP << 8 | WHO_AM_I_M_RSP) ); } void LSM9DS1::initGyro() { + /* char cmd[4] = { CTRL_REG1_G, gScale | G_ODR_119_BW_14, 0, // Default data out and int out 0 // Default power mode and high pass settings }; + */ + + char cmd[4] = { + CTRL_REG1_G, + gScale | G_ODR_119_BW_14, + 0x03, // Data pass througn HPF and LPF2, default int out + 0x80 // Low-power mode, disable high-pass filter, default cut-off frequency + }; // Write the data to the gyro control registers i2c.write(xgAddress, cmd, 4); @@ -103,7 +112,7 @@ } void LSM9DS1::initMag() -{ +{ char cmd[4] = { CTRL_REG1_M, 0x10, // Default data rate, xy axes mode, and temp comp @@ -116,14 +125,14 @@ } void LSM9DS1::initIntr() -{ +{ char cmd[2]; uint16_t thresholdG = 500; uint8_t durationG = 10; uint8_t thresholdX = 20; uint8_t durationX = 1; uint16_t thresholdM = 10000; - + // 1. Configure the gyro interrupt generator cmd[0] = INT_GEN_CFG_G; cmd[1] = (1 << 5); @@ -138,7 +147,7 @@ cmd[0] = INT_GEN_DUR_G; cmd[1] = (durationG & 0x7F) | 0x80; i2c.write(xgAddress, cmd, 2); - + // 3. Configure accelerometer interrupt generator cmd[0] = INT_GEN_CFG_XL; cmd[1] = (1 << 1); @@ -150,7 +159,7 @@ cmd[0] = INT_GEN_DUR_XL; cmd[1] = (durationX & 0x7F); i2c.write(xgAddress, cmd, 2); - + // 5. Configure INT1 - assign it to gyro interrupt cmd[0] = INT1_CTRL; // cmd[1] = 0xC0; @@ -160,7 +169,7 @@ // cmd[1] = 0x04; cmd[1] = (1 << 2) | (1 << 5) | (1 << 4); i2c.write(xgAddress, cmd, 2); - + // Configure interrupt 2 to fire whenever new accelerometer // or gyroscope data is available. cmd[0] = INT2_CTRL; @@ -169,12 +178,12 @@ cmd[0] = CTRL_REG8; cmd[1] = (1 << 2) | (1 << 5) | (1 << 4); i2c.write(xgAddress, cmd, 2); - + // Configure magnetometer interrupt cmd[0] = INT_CFG_M; cmd[1] = (1 << 7) | (1 << 0); i2c.write(xgAddress, cmd, 2); - + // Configure magnetometer threshold cmd[0] = INT_THS_H_M; cmd[1] = uint8_t((thresholdM & 0x7F00) >> 8); @@ -186,65 +195,75 @@ void LSM9DS1::calibration() { - + uint16_t samples = 0; int32_t aBiasRawTemp[3] = {0, 0, 0}; int32_t gBiasRawTemp[3] = {0, 0, 0}; - /* - // Turn on FIFO and set threshold to 32 samples - enableXgFIFO(true); - setXgFIFO( 1, 0x1F); - while (samples < 0x1F) - { - samples = (i2c.read(FIFO_SRC) & 0x3F); // Read number of stored samples - } - for(int ii = 0; ii < samples ; ii++) - { // Read the gyro data stored in the FIFO - readGyro(); - gBiasRawTemp[0] += gx_raw; - gBiasRawTemp[1] += gy_raw; - gBiasRawTemp[2] += gz_raw; - readAccel(); - aBiasRawTemp[0] += ax_raw; - aBiasRawTemp[1] += ay_raw; - aBiasRawTemp[2] += az_raw - (int16_t)(1./aRes); // Assumes sensor facing up! - } - for (int ii = 0; ii < 3; ii++) - { - gBias_raw[ii] = gBiasRawTemp[ii] / samples; - gBias[ii] = gBias_raw[ii] * gRes; - aBias_raw[ii] = aBiasRawTemp[ii] / samples; - aBias[ii] = aBias_raw[ii] * aRes; - } - - - - enableXgFIFO(false); - setXgFIFO(0, 0x00); - */ - while(samples < 300) + int32_t gDeltaBiasRaw[3] = {0, 0, 0}; + + // Turn off the autoCalib first to get the raw data + autoCalib = false; + + while(samples < 200) { readGyro(); gBiasRawTemp[0] += gx_raw; gBiasRawTemp[1] += gy_raw; gBiasRawTemp[2] += gz_raw; + /* readAccel(); aBiasRawTemp[0] += ax_raw; aBiasRawTemp[1] += ay_raw; aBiasRawTemp[2] += az_raw; - wait_us(1000); - samples++; + */ + wait_ms(1); // 1 ms + samples++; } - + + // for(int ii = 0; ii < 3; ii++) { gBiasRaw[ii] = gBiasRawTemp[ii] / samples; - aBiasRaw[ii] = aBiasRawTemp[ii] / samples; - + // aBiasRaw[ii] = aBiasRawTemp[ii] / samples; + aBiasRaw[ii] = 0; + + /* gBias[ii] = gBiasRaw[ii] * gRes; - aBias[ii] = aBiasRaw[ii] * aRes; + aBias[ii] = aBiasRaw[ii] * aRes; + */ + gBiasRawTemp[ii] = 0; } - + + // Re-calculate the bias + int bound_bias = 10; + int32_t samples_axis[3] = {0, 0, 0}; + for (size_t i = 0; i < 500; ++i){ + readGyro(); + gDeltaBiasRaw[0] = gx_raw - gBiasRaw[0]; + gDeltaBiasRaw[1] = gy_raw - gBiasRaw[1]; + gDeltaBiasRaw[2] = gz_raw - gBiasRaw[2]; + for (size_t k = 0; k < 3; ++k){ + if (gDeltaBiasRaw[k] >= -bound_bias && gDeltaBiasRaw[k] <= bound_bias){ + gBiasRawTemp[k] += gDeltaBiasRaw[k] + gBiasRaw[k]; + samples_axis[k]++; + } + } + // + wait_ms(1); // 1 ms + } + + // + for(int ii = 0; ii < 3; ii++) + { + gBiasRaw[ii] = gBiasRawTemp[ii] / samples_axis[ii]; + // aBiasRaw[ii] = aBiasRawTemp[ii] / samples; + aBiasRaw[ii] = 0; + + gBias[ii] = gBiasRaw[ii] * gRes; + aBias[ii] = aBiasRaw[ii] * aRes; + } + + // Turn on the autoCalib autoCalib = true; } @@ -265,20 +284,18 @@ ax_raw = data[0] | (data[1] << 8); ay_raw = data[2] | (data[3] << 8); az_raw = data[4] | (data[5] << 8); - ax = ax_raw * aRes; - ay = ay_raw * aRes; - az = az_raw * aRes; - - if(autoCalib == true) + + // + if(autoCalib) { ax_raw -= aBiasRaw[0]; ay_raw -= aBiasRaw[1]; az_raw -= aBiasRaw[2]; - - ax = ax_raw * aRes; - ay = ay_raw * aRes; - az = az_raw * aRes; } + // + ax = ax_raw * aRes; + ay = ay_raw * aRes; + az = az_raw * aRes; } void LSM9DS1::readMag() @@ -327,7 +344,7 @@ // Read in all 8 bit registers containing the axes data i2c.read(xgAddress, data, 2); - // Temperature is a 12-bit signed integer + // Temperature is a 12-bit signed integer temperature_raw = data[0] | (data[1] << 8); temperature_c = (float)temperature_raw / 8.0 + 25; @@ -352,21 +369,19 @@ gx_raw = data[0] | (data[1] << 8); gy_raw = data[2] | (data[3] << 8); gz_raw = data[4] | (data[5] << 8); - gx = gx_raw * gRes; - gy = gy_raw * gRes; - gz = gz_raw * gRes; - - if(autoCalib == true) + + // + if(autoCalib) { gx_raw -= gBiasRaw[0]; gy_raw -= gBiasRaw[1]; gz_raw -= gBiasRaw[2]; - - gx = gx_raw * gRes; - gy = gy_raw * gRes; - gz = gz_raw * gRes; } - + // + gx = gx_raw * gRes; + gy = gy_raw * gRes; + gz = gz_raw * gRes; + } void LSM9DS1::setGyroScale(gyro_scale gScl) @@ -389,7 +404,7 @@ // Write the gyroscale out to the gyro i2c.write(xgAddress, cmd, 2); - + // We've updated the sensor, but we also need to update our class variables // First update gScale: gScale = gScl; @@ -417,7 +432,7 @@ // Write the accelscale out to the accel i2c.write(xgAddress, cmd, 2); - + // We've updated the sensor, but we also need to update our class variables // First update aScale: aScale = aScl; @@ -445,7 +460,7 @@ // Write the magscale out to the mag i2c.write(mAddress, cmd, 2); - + // We've updated the sensor, but we also need to update our class variables // First update mScale: mScale = mScl; @@ -457,14 +472,15 @@ { char cmd[2]; char cmdLow[2]; - + + // Enable the low-power mode if(gRate == G_ODR_15_BW_0 | gRate == G_ODR_60_BW_16 | gRate == G_ODR_119_BW_14 | gRate == G_ODR_119_BW_31) { cmdLow[0] = CTRL_REG3_G; - cmdLow[1] = 1; - + cmdLow[1] = 1<<7; // LP_mode + i2c.write(xgAddress, cmdLow, 2); } - + // The start of the addresses we want to read from cmd[0] = CTRL_REG1_G; cmd[1] = 0; @@ -534,13 +550,16 @@ switch (gScale) { case G_SCALE_245DPS: - gRes = 245.0 / 32768.0; + // gRes = 245.0 / 32768.0; + gRes = 8.75*0.001; // deg./sec. break; case G_SCALE_500DPS: - gRes = 500.0 / 32768.0; + // gRes = 500.0 / 32768.0; + gRes = 17.50*0.001; // deg./sec. break; case G_SCALE_2000DPS: - gRes = 2000.0 / 32768.0; + // gRes = 2000.0 / 32768.0; + gRes = 70.0*0.001; // deg./sec. break; } } @@ -561,7 +580,8 @@ aRes = 8.0 / 32768.0; break; case A_SCALE_16G: - aRes = 16.0 / 32768.0; + // aRes = 16.0 / 32768.0; + aRes = 0.732*0.001; // g (gravity) break; } } @@ -569,20 +589,24 @@ void LSM9DS1::calcmRes() { // Possible magnetometer scales (and their register bit settings) are: - // 2 Gs (00), 4 Gs (01), 8 Gs (10) 12 Gs (11). + // 2 Gs (00), 4 Gs (01), 8 Gs (10) 12 Gs (11). switch (mScale) { case M_SCALE_4GS: - mRes = 4.0 / 32768.0; + // mRes = 4.0 / 32768.0; + mRes = 0.14*0.001; // gauss break; case M_SCALE_8GS: - mRes = 8.0 / 32768.0; + // mRes = 8.0 / 32768.0; + mRes = 0.29*0.001; // gauss break; case M_SCALE_12GS: - mRes = 12.0 / 32768.0; + // mRes = 12.0 / 32768.0; + mRes = 0.43*0.001; // gauss break; case M_SCALE_16GS: - mRes = 16.0 / 32768.0; + // mRes = 16.0 / 32768.0; + mRes = 0.58*0.001; // gauss break; } } @@ -592,13 +616,13 @@ void LSM9DS1::enableXgFIFO(bool enable) { char cmd[2] = {CTRL_REG9, 0}; - + i2c.write(xgAddress, cmd, 1); cmd[1] = i2c.read(CTRL_REG9); - + if (enable) cmd[1] |= (1<<1); else cmd[1] &= ~(1<<1); - + i2c.write(xgAddress, cmd, 2); } @@ -611,4 +635,4 @@ cmd[1] = ((fifoMode & 0x7) << 5) | (threshold & 0x1F); i2c.write(xgAddress, cmd, 2); } -*/ \ No newline at end of file +*/
diff -r e6e3d320eb6c -r 16e88babd42a LSM9DS1.h --- a/LSM9DS1.h Thu Jun 15 08:42:23 2017 +0000 +++ b/LSM9DS1.h Mon Jun 19 02:25:48 2017 +0000 @@ -104,12 +104,20 @@ public: /// gyro_scale defines the possible full-scale ranges of the gyroscope: + /* enum gyro_scale { G_SCALE_245DPS = 0x0 << 3, // 00 << 3: +/- 245 degrees per second G_SCALE_500DPS = 0x1 << 3, // 01 << 3: +/- 500 dps G_SCALE_2000DPS = 0x3 << 3 // 11 << 3: +/- 2000 dps }; + */ + enum gyro_scale + { + G_SCALE_245DPS = 0x0, // 00: +/- 245 degrees per second + G_SCALE_500DPS = 0x1, // 01: +/- 500 dps + G_SCALE_2000DPS = 0x3 // 11: +/- 2000 dps + }; /// gyro_odr defines all possible data rate/bandwidth combos of the gyro: enum gyro_odr @@ -195,7 +203,7 @@ int16_t ax_raw, ay_raw, az_raw; // x, y, and z axis readings of the accelerometer int16_t mx_raw, my_raw, mz_raw; // x, y, and z axis readings of the magnetometer int16_t temperature_raw; - int16_t gBiasRaw[3], aBiasRaw[3]; + int16_t gBiasRaw[3], aBiasRaw[3]; // floating-point values of scaled data in real-world units float gx, gy, gz; @@ -206,7 +214,7 @@ float gBias[3], aBias[3]; bool autoCalib; - + /** LSM9DS1 -- LSM9DS1 class constructor * The constructor will set up a handful of private variables, and set the * communication mode as well. @@ -219,7 +227,7 @@ * If MODE_SPI, this is the cs pin of the mag (CS_M) */ LSM9DS1(PinName sda, PinName scl, uint8_t xgAddr = LSM9DS1_AG_I2C_ADDR(1), uint8_t mAddr = LSM9DS1_M_I2C_ADDR(1)); - + /** begin() -- Initialize the gyro, accelerometer, and magnetometer. * This will set up the scale and output rate of each sensor. It'll also * "turn on" every sensor and every axis of every sensor. @@ -234,59 +242,59 @@ * bytes of the output are the WHO_AM_I reading of the accel/gyro. The * least significant two bytes are the WHO_AM_I reading of the mag. * All parameters have a defaulted value, so you can call just "begin()". - * Default values are FSR's of: +/- 245DPS, 4g, 2Gs; ODRs of 119 Hz for + * Default values are FSR's of: +/- 245DPS, 4g, 2Gs; ODRs of 119 Hz for * gyro, 119 Hz for accelerometer, 80 Hz for magnetometer. * Use the return value of this function to verify communication. */ - uint16_t begin(gyro_scale gScl = G_SCALE_500DPS, - accel_scale aScl = A_SCALE_2G, mag_scale mScl = M_SCALE_4GS, - gyro_odr gODR = G_ODR_952_BW_100, accel_odr aODR = A_ODR_119, + bool begin(gyro_scale gScl = G_SCALE_2000DPS, + accel_scale aScl = A_SCALE_8G, mag_scale mScl = M_SCALE_4GS, + gyro_odr gODR = G_ODR_119_BW_31, accel_odr aODR = A_ODR_119, mag_odr mODR = M_ODR_80); - + /** readGyro() -- Read the gyroscope output registers. * This function will read all six gyroscope output registers. * The readings are stored in the class' gx_raw, gy_raw, and gz_raw variables. Read * those _after_ calling readGyro(). */ void readGyro(); - + /** readAccel() -- Read the accelerometer output registers. * This function will read all six accelerometer output registers. * The readings are stored in the class' ax_raw, ay_raw, and az_raw variables. Read * those _after_ calling readAccel(). */ void readAccel(); - + /** readMag() -- Read the magnetometer output registers. * This function will read all six magnetometer output registers. * The readings are stored in the class' mx_raw, my_raw, and mz_raw variables. Read * those _after_ calling readMag(). */ void readMag(); - + /** Read Interrupt **/ void readIntr(); - + /** readTemp() -- Read the temperature output register. * This function will read two temperature output registers. * The combined readings are stored in the class' temperature variables. Read * those _after_ calling readTemp(). */ void readTemp(); - + /** calibration() -- Calibrate Accel and Gyro sensor */ void calibration(); - + /** setGyroScale() -- Set the full-scale range of the gyroscope. - * This function can be called to set the scale of the gyroscope to + * This function can be called to set the scale of the gyroscope to * 245, 500, or 2000 degrees per second. * Input: * - gScl = The desired gyroscope scale. Must be one of three possible * values from the gyro_scale enum. */ void setGyroScale(gyro_scale gScl); - + /** setAccelScale() -- Set the full-scale range of the accelerometer. * This function can be called to set the scale of the accelerometer to * 2, 4, 8, or 16 g's. @@ -295,7 +303,7 @@ * values from the accel_scale enum. */ void setAccelScale(accel_scale aScl); - + /** setMagScale() -- Set the full-scale range of the magnetometer. * This function can be called to set the scale of the magnetometer to * 4, 8, 12, or 16 Gs. @@ -304,29 +312,29 @@ * values from the mag_scale enum. */ void setMagScale(mag_scale mScl); - + /** setGyroODR() -- Set the output data rate and bandwidth of the gyroscope * Input: * - gRate = The desired output rate and cutoff frequency of the gyro. * Must be a value from the gyro_odr enum (check above). */ void setGyroODR(gyro_odr gRate); - + /** setAccelODR() -- Set the output data rate of the accelerometer * Input: * - aRate = The desired output rate of the accel. * Must be a value from the accel_odr enum (check above). */ void setAccelODR(accel_odr aRate); - + /** setMagODR() -- Set the output data rate of the magnetometer * Input: * - mRate = The desired output rate of the mag. * Must be a value from the mag_odr enum (check above). */ void setMagODR(mag_odr mRate); - - + + /** enableFIFO() -- Turn on FIFO state (CTRL_REG9) * Input: * - enable = true - turn on FIFO @@ -342,58 +350,58 @@ */ void setXgFIFO(uint8_t fifoMode, uint8_t fifoThs); -private: +private: /** xgAddress and mAddress store the I2C address * for each sensor. */ uint8_t xgAddress, mAddress; - + // I2C bus I2C i2c; - /** gScale, aScale, and mScale store the current scale range for each + /** gScale, aScale, and mScale store the current scale range for each * sensor. Should be updated whenever that value changes. */ gyro_scale gScale; accel_scale aScale; mag_scale mScale; - - /** gRes, aRes, and mRes store the current resolution for each sensor. + + /** gRes, aRes, and mRes store the current resolution for each sensor. * Units of these values would be DPS (or g's or Gs's) per ADC tick. * This value is calculated as (sensor scale) / (2^15). */ float gRes, aRes, mRes; - + /** initGyro() -- Sets up the gyroscope to begin reading. * This function steps through all three gyroscope control registers. */ void initGyro(); - + /** initAccel() -- Sets up the accelerometer to begin reading. * This function steps through all accelerometer related control registers. */ void initAccel(); - + /** Initialize Interrupts **/ void initIntr(); - + /** initMag() -- Sets up the magnetometer to begin reading. * This function steps through all magnetometer-related control registers. */ void initMag(); - + /** calcgRes() -- Calculate the resolution of the gyroscope. * This function will set the value of the gRes variable. gScale must * be set prior to calling this function. */ void calcgRes(); - + /** calcmRes() -- Calculate the resolution of the magnetometer. * This function will set the value of the mRes variable. mScale must * be set prior to calling this function. */ void calcmRes(); - + /** calcaRes() -- Calculate the resolution of the accelerometer. * This function will set the value of the aRes variable. aScale must * be set prior to calling this function.
diff -r e6e3d320eb6c -r 16e88babd42a main.cpp --- a/main.cpp Thu Jun 15 08:42:23 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -#include "LSM9DS1.h" - -Serial pc(USBTX, USBRX); - -DigitalOut myled(LED1); - -Ticker timer1; -void initTimer(); -void timer1_interrupt(); - -float gyroZCounter = 0; -LSM9DS1 imu(D14, D15, 0xD6, 0x3C); - -unsigned long long sysTime = 0; -unsigned long long last_time = 0; -#define Period 10 // 10 ms - - -int main() -{ - - pc.baud(115200); - - pc.printf("Begin LSM9DS1.\n\r"); - - if (!imu.begin()) - { - pc.printf("Failed to communicate with LSM9DS1.\n\r"); - } - - imu.calibration(); - pc.printf("calibration: %d\n\r", imu.autoCalib); - - initTimer(); - - while(1) - { - if (sysTime - last_time >= Period) - { - last_time = sysTime; - - myled = 0; - imu.readGyro(); - myled = 1; - - gyroZCounter += imu.gz * 0.01; - - pc.printf("g: %f, cnt: %f\n\r", imu.gz, gyroZCounter); - - } - } -} - -void initTimer() -{ - timer1.attach_us(&timer1_interrupt, 1000); // 1ms interrupt period (1 KHz) -} - -void timer1_interrupt() -{ - sysTime++; -} \ No newline at end of file
diff -r e6e3d320eb6c -r 16e88babd42a mbed.bld --- a/mbed.bld Thu Jun 15 08:42:23 2017 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -https://mbed.org/users/mbed_official/code/mbed/builds/0f02307a0877 \ No newline at end of file