mirror of https://github.com/ArduPilot/ardupilot
187 lines
5.7 KiB
C++
187 lines
5.7 KiB
C++
/*
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "AP_RangeFinder_PulsedLightLRF.h"
|
|
|
|
#include <utility>
|
|
#include <stdio.h>
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
|
#include <AP_HAL/utility/sparse-endian.h>
|
|
|
|
extern const AP_HAL::HAL& hal;
|
|
|
|
/* LL40LS Registers addresses */
|
|
#define LL40LS_MEASURE_REG 0x00 /* Measure range register */
|
|
#define LL40LS_DISTHIGH_REG 0x0F /* High byte of distance register, auto increment */
|
|
#define LL40LS_COUNT 0x11
|
|
#define LL40LS_HW_VERSION 0x41
|
|
#define LL40LS_INTERVAL 0x45
|
|
#define LL40LS_SW_VERSION 0x4f
|
|
|
|
// bit values
|
|
#define LL40LS_MSRREG_RESET 0x00 /* reset to power on defaults */
|
|
#define LL40LS_AUTO_INCREMENT 0x80
|
|
#define LL40LS_COUNT_CONTINUOUS 0xff
|
|
#define LL40LS_MSRREG_ACQUIRE 0x04 /* Value to initiate a measurement, varies based on sensor revision */
|
|
|
|
// i2c address
|
|
#define LL40LS_ADDR 0x62
|
|
|
|
/*
|
|
The constructor also initializes the rangefinder. Note that this
|
|
constructor is not called until detect() returns true, so we
|
|
already know that we should setup the rangefinder
|
|
*/
|
|
AP_RangeFinder_PulsedLightLRF::AP_RangeFinder_PulsedLightLRF(uint8_t bus, RangeFinder &_ranger, uint8_t instance,
|
|
RangeFinder::RangeFinder_State &_state)
|
|
: AP_RangeFinder_Backend(_ranger, instance, _state)
|
|
, _dev(hal.i2c_mgr->get_device(bus, LL40LS_ADDR))
|
|
{
|
|
}
|
|
|
|
/*
|
|
detect if a PulsedLight rangefinder is connected. We'll detect by
|
|
look for the version registers
|
|
*/
|
|
AP_RangeFinder_Backend *AP_RangeFinder_PulsedLightLRF::detect(uint8_t bus, RangeFinder &_ranger, uint8_t instance,
|
|
RangeFinder::RangeFinder_State &_state)
|
|
{
|
|
AP_RangeFinder_PulsedLightLRF *sensor
|
|
= new AP_RangeFinder_PulsedLightLRF(bus, _ranger, instance, _state);
|
|
if (!sensor ||
|
|
!sensor->init()) {
|
|
delete sensor;
|
|
return nullptr;
|
|
}
|
|
return sensor;
|
|
}
|
|
|
|
/*
|
|
called at 50Hz
|
|
*/
|
|
void AP_RangeFinder_PulsedLightLRF::timer(void)
|
|
{
|
|
if (check_reg_counter++ == 10) {
|
|
check_reg_counter = 0;
|
|
if (!_dev->check_next_register()) {
|
|
// re-send the acquire. this handles the case of power
|
|
// cycling while running in continuous mode
|
|
_dev->write_register(LL40LS_MEASURE_REG, LL40LS_MSRREG_ACQUIRE);
|
|
}
|
|
}
|
|
|
|
switch (phase) {
|
|
case PHASE_MEASURE:
|
|
if (_dev->write_register(LL40LS_MEASURE_REG, LL40LS_MSRREG_ACQUIRE)) {
|
|
phase = PHASE_COLLECT;
|
|
}
|
|
break;
|
|
|
|
case PHASE_COLLECT: {
|
|
be16_t val;
|
|
// read the high and low byte distance registers
|
|
if (_dev->read_registers(LL40LS_DISTHIGH_REG | LL40LS_AUTO_INCREMENT, (uint8_t*)&val, sizeof(val))) {
|
|
uint16_t distance_cm = be16toh(val);
|
|
// remove momentary spikes
|
|
if (abs(distance_cm - last_distance_cm) < 100) {
|
|
state.distance_cm = distance_cm;
|
|
update_status();
|
|
}
|
|
last_distance_cm = distance_cm;
|
|
} else {
|
|
set_status(RangeFinder::RangeFinder_NoData);
|
|
}
|
|
if (!v2_hardware) {
|
|
// for v2 hw we use continuous mode
|
|
phase = PHASE_MEASURE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
a table of settings for a lidar
|
|
*/
|
|
struct settings_table {
|
|
uint8_t reg;
|
|
uint8_t value;
|
|
};
|
|
|
|
/*
|
|
register setup table for V1 Lidar
|
|
*/
|
|
static const struct settings_table settings_v1[] = {
|
|
{ LL40LS_MEASURE_REG, LL40LS_MSRREG_RESET },
|
|
};
|
|
|
|
/*
|
|
register setup table for V2 Lidar
|
|
*/
|
|
static const struct settings_table settings_v2[] = {
|
|
{ LL40LS_INTERVAL, 0x28 }, // 0x28 == 50Hz
|
|
{ LL40LS_COUNT, LL40LS_COUNT_CONTINUOUS },
|
|
{ LL40LS_MEASURE_REG, LL40LS_MSRREG_ACQUIRE },
|
|
};
|
|
|
|
/*
|
|
initialise the sensor to required settings
|
|
*/
|
|
bool AP_RangeFinder_PulsedLightLRF::init(void)
|
|
{
|
|
if (!_dev || !_dev->get_semaphore()->take(HAL_SEMAPHORE_BLOCK_FOREVER)) {
|
|
return false;
|
|
}
|
|
_dev->set_retries(3);
|
|
|
|
if (!(_dev->read_registers(LL40LS_HW_VERSION, &hw_version, 1) &&
|
|
hw_version > 0 &&
|
|
_dev->read_registers(LL40LS_SW_VERSION, &sw_version, 1) &&
|
|
sw_version > 0)) {
|
|
printf("PulsedLightI2C: bad version 0x%02x 0x%02x\n", (unsigned)hw_version, (unsigned)sw_version);
|
|
// invalid version information
|
|
_dev->get_semaphore()->give();
|
|
return false;
|
|
}
|
|
_dev->get_semaphore()->give();
|
|
|
|
v2_hardware = (hw_version >= 0x15);
|
|
|
|
const struct settings_table *table;
|
|
uint8_t num_settings;
|
|
|
|
if (v2_hardware) {
|
|
table = settings_v2;
|
|
num_settings = sizeof(settings_v2) / sizeof(settings_table);
|
|
phase = PHASE_COLLECT;
|
|
} else {
|
|
table = settings_v1;
|
|
num_settings = sizeof(settings_v1) / sizeof(settings_table);
|
|
phase = PHASE_MEASURE;
|
|
}
|
|
|
|
_dev->setup_checked_registers(num_settings);
|
|
|
|
for (uint8_t i = 0; i < num_settings; i++) {
|
|
_dev->write_register(table[i].reg, table[i].value, true);
|
|
}
|
|
|
|
_dev->register_periodic_callback(20000,
|
|
FUNCTOR_BIND_MEMBER(&AP_RangeFinder_PulsedLightLRF::timer, void));
|
|
return true;
|
|
}
|
|
|