/**************************************************************************** * * Copyright (c) 2013, 2014 PX4 Development Team. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name PX4 nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /** * @file ets_airspeed.cpp * @author Simon Wilks * * Driver for the Eagle Tree Airspeed V3 connected via I2C. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* I2C bus address */ #define I2C_ADDRESS 0x75 /* 7-bit address. 8-bit address is 0xEA */ #define ETS_PATH "/dev/ets_airspeed" /* Register address */ #define READ_CMD 0x07 /* Read the data */ /** * The Eagle Tree Airspeed V3 cannot provide accurate reading below speeds of 15km/h. * You can set this value to 12 if you want a zero reading below 15km/h. */ #define MIN_ACCURATE_DIFF_PRES_PA 0 /* Measurement rate is 100Hz */ #define CONVERSION_INTERVAL (1000000 / 100) /* microseconds */ class ETSAirspeed : public Airspeed { public: ETSAirspeed(int bus, int address = I2C_ADDRESS, const char* path = ETS_PATH); protected: /** * Perform a poll cycle; collect from the previous measurement * and start a new one. */ virtual void cycle(); virtual int measure(); virtual int collect(); }; /* * Driver 'main' command. */ extern "C" __EXPORT int ets_airspeed_main(int argc, char *argv[]); ETSAirspeed::ETSAirspeed(int bus, int address, const char* path) : Airspeed(bus, address, CONVERSION_INTERVAL, path) { } int ETSAirspeed::measure() { int ret; /* * Send the command to begin a measurement. */ uint8_t cmd = READ_CMD; ret = transfer(&cmd, 1, nullptr, 0); if (OK != ret) { perf_count(_comms_errors); } return ret; } int ETSAirspeed::collect() { int ret = -EIO; /* read from the sensor */ uint8_t val[2] = {0, 0}; perf_begin(_sample_perf); ret = transfer(nullptr, 0, &val[0], 2); if (ret < 0) { perf_count(_comms_errors); return ret; } uint16_t diff_pres_pa_raw = val[1] << 8 | val[0]; if (diff_pres_pa_raw == 0) { // a zero value means the pressure sensor cannot give us a // value. We need to return, and not report a value or the // caller could end up using this value as part of an // average perf_count(_comms_errors); log("zero value from sensor"); return -1; } // The raw value still should be compensated for the known offset diff_pres_pa_raw -= _diff_pres_offset; // Track maximum differential pressure measured (so we can work out top speed). if (diff_pres_pa_raw > _max_differential_pressure_pa) { _max_differential_pressure_pa = diff_pres_pa_raw; } differential_pressure_s report; report.timestamp = hrt_absolute_time(); report.error_count = perf_event_count(_comms_errors); // XXX we may want to smooth out the readings to remove noise. report.differential_pressure_filtered_pa = diff_pres_pa_raw; report.differential_pressure_raw_pa = diff_pres_pa_raw; report.temperature = -1000.0f; report.max_differential_pressure_pa = _max_differential_pressure_pa; if (_airspeed_pub > 0 && !(_pub_blocked)) { /* publish it */ orb_publish(ORB_ID(differential_pressure), _airspeed_pub, &report); } new_report(report); /* notify anyone waiting for data */ poll_notify(POLLIN); ret = OK; perf_end(_sample_perf); return ret; } void ETSAirspeed::cycle() { int ret; /* collection phase? */ if (_collect_phase) { /* perform collection */ ret = collect(); if (OK != ret) { perf_count(_comms_errors); /* restart the measurement state machine */ start(); _sensor_ok = false; return; } /* next phase is measurement */ _collect_phase = false; /* * Is there a collect->measure gap? */ if (_measure_ticks > USEC2TICK(CONVERSION_INTERVAL)) { /* schedule a fresh cycle call when we are ready to measure again */ work_queue(HPWORK, &_work, (worker_t)&Airspeed::cycle_trampoline, this, _measure_ticks - USEC2TICK(CONVERSION_INTERVAL)); return; } } /* measurement phase */ ret = measure(); if (OK != ret) { debug("measure error"); } _sensor_ok = (ret == OK); /* next phase is collection */ _collect_phase = true; /* schedule a fresh cycle call when the measurement is done */ work_queue(HPWORK, &_work, (worker_t)&Airspeed::cycle_trampoline, this, USEC2TICK(CONVERSION_INTERVAL)); } /** * Local functions in support of the shell command. */ namespace ets_airspeed { /* oddly, ERROR is not defined for c++ */ #ifdef ERROR # undef ERROR #endif const int ERROR = -1; ETSAirspeed *g_dev; void start(int i2c_bus); void stop(); void test(); void reset(); void info(); /** * Start the driver. * * This function only returns if the sensor is up and running * or could not be detected successfully. */ void start(int i2c_bus) { int fd; if (g_dev != nullptr) errx(1, "already started"); /* create the driver */ g_dev = new ETSAirspeed(i2c_bus); if (g_dev == nullptr) goto fail; if (OK != g_dev->Airspeed::init()) goto fail; /* set the poll rate to default, starts automatic data collection */ fd = open(AIRSPEED0_DEVICE_PATH, O_RDONLY); if (fd < 0) goto fail; if (ioctl(fd, SENSORIOCSPOLLRATE, SENSOR_POLLRATE_DEFAULT) < 0) goto fail; exit(0); fail: if (g_dev != nullptr) { delete g_dev; g_dev = nullptr; } errx(1, "no ETS airspeed sensor connected"); } /** * Stop the driver */ void stop() { if (g_dev != nullptr) { delete g_dev; g_dev = nullptr; } else { errx(1, "driver not running"); } exit(0); } /** * Perform some basic functional tests on the driver; * make sure we can collect data from the sensor in polled * and automatic modes. */ void test() { struct differential_pressure_s report; ssize_t sz; int ret; int fd = open(ETS_PATH, O_RDONLY); if (fd < 0) err(1, "%s open failed (try 'ets_airspeed start' if the driver is not running", ETS_PATH); /* do a simple demand read */ sz = read(fd, &report, sizeof(report)); if (sz != sizeof(report)) err(1, "immediate read failed"); warnx("single read"); warnx("diff pressure: %f pa", (double)report.differential_pressure_filtered_pa); /* start the sensor polling at 2Hz */ if (OK != ioctl(fd, SENSORIOCSPOLLRATE, 2)) errx(1, "failed to set 2Hz poll rate"); /* read the sensor 5x and report each value */ for (unsigned i = 0; i < 5; i++) { struct pollfd fds; /* wait for data to be ready */ fds.fd = fd; fds.events = POLLIN; ret = poll(&fds, 1, 2000); if (ret != 1) errx(1, "timed out waiting for sensor data"); /* now go get it */ sz = read(fd, &report, sizeof(report)); if (sz != sizeof(report)) err(1, "periodic read failed"); warnx("periodic read %u", i); warnx("diff pressure: %f pa", (double)report.differential_pressure_filtered_pa); } /* reset the sensor polling to its default rate */ if (OK != ioctl(fd, SENSORIOCSPOLLRATE, SENSOR_POLLRATE_DEFAULT)) errx(1, "failed to set default rate"); errx(0, "PASS"); } /** * Reset the driver. */ void reset() { int fd = open(ETS_PATH, O_RDONLY); if (fd < 0) err(1, "failed "); if (ioctl(fd, SENSORIOCRESET, 0) < 0) err(1, "driver reset failed"); if (ioctl(fd, SENSORIOCSPOLLRATE, SENSOR_POLLRATE_DEFAULT) < 0) err(1, "driver poll restart failed"); exit(0); } /** * Print a little info about the driver. */ void info() { if (g_dev == nullptr) errx(1, "driver not running"); printf("state @ %p\n", g_dev); g_dev->print_info(); exit(0); } } // namespace static void ets_airspeed_usage() { warnx("usage: ets_airspeed command [options]"); warnx("options:"); warnx("\t-b --bus i2cbus (%d)", PX4_I2C_BUS_DEFAULT); warnx("command:"); warnx("\tstart|stop|reset|test|info"); } int ets_airspeed_main(int argc, char *argv[]) { int i2c_bus = PX4_I2C_BUS_DEFAULT; int i; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--bus") == 0) { if (argc > i + 1) { i2c_bus = atoi(argv[i + 1]); } } } /* * Start/load the driver. */ if (!strcmp(argv[1], "start")) ets_airspeed::start(i2c_bus); /* * Stop the driver */ if (!strcmp(argv[1], "stop")) ets_airspeed::stop(); /* * Test the driver/device. */ if (!strcmp(argv[1], "test")) ets_airspeed::test(); /* * Reset the driver. */ if (!strcmp(argv[1], "reset")) ets_airspeed::reset(); /* * Print driver information. */ if (!strcmp(argv[1], "info") || !strcmp(argv[1], "status")) ets_airspeed::info(); ets_airspeed_usage(); exit(0); }