You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
13 KiB
477 lines
13 KiB
/* |
|
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_HAL/AP_HAL.h> |
|
#include <utility> |
|
|
|
#if (CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_BEBOP || \ |
|
CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_DISCO) && \ |
|
defined(HAVE_LIBIIO) |
|
|
|
#include <stdlib.h> |
|
#include <unistd.h> |
|
#include <stdio.h> |
|
#include <fcntl.h> |
|
#include <string.h> |
|
#include <sys/mman.h> |
|
#include <linux/types.h> |
|
#include <errno.h> |
|
#include <sys/ioctl.h> |
|
#include <float.h> |
|
#include <math.h> |
|
#include <time.h> |
|
#include <iio.h> |
|
#include "AP_RangeFinder_Bebop.h" |
|
#include <AP_HAL_Linux/Thread.h> |
|
#include <AP_HAL_Linux/GPIO.h> |
|
|
|
/* |
|
* this mode is used at low altitude |
|
* send 4 wave patterns |
|
* gpio in low mode |
|
*/ |
|
#define RNFD_BEBOP_DEFAULT_MODE 1 |
|
|
|
/* |
|
* the number of p7s in the iio buffer |
|
*/ |
|
#define RNFD_BEBOP_P7_COUNT 8192 |
|
|
|
extern const AP_HAL::HAL& hal; |
|
|
|
static const uint16_t waveform_mode0[14] = { |
|
4000, 3800, 3600, 3400, 3200, 3000, 2800, |
|
2600, 2400, 2200, 2000, 1800, 1600, 1400, |
|
}; |
|
|
|
static const uint16_t waveform_mode1[32] = { |
|
4190, 4158, 4095, 4095, 4095, 4095, 4095, 4095, 4095, |
|
4095, 4090, 4058, 3943, 3924, 3841, 3679, 3588, 3403, |
|
3201, 3020, 2816, 2636, 2448, 2227, 2111, 1955, 1819, |
|
1675, 1540, 1492, 1374, 1292 |
|
}; |
|
|
|
AP_RangeFinder_Bebop::AP_RangeFinder_Bebop(RangeFinder::RangeFinder_State &_state, AP_RangeFinder_Params &_params) : |
|
AP_RangeFinder_Backend(_state, _params), |
|
_thread(new Linux::Thread(FUNCTOR_BIND_MEMBER(&AP_RangeFinder_Bebop::_loop, void))) |
|
{ |
|
_init(); |
|
_freq = RNFD_BEBOP_DEFAULT_FREQ; |
|
_filtered_capture_size = _adc.buffer_size / _filter_average; |
|
_filtered_capture = (unsigned int*) calloc(1, sizeof(_filtered_capture[0]) * |
|
_filtered_capture_size); |
|
_mode = RNFD_BEBOP_DEFAULT_MODE; |
|
/* SPI and IIO can not be initialized just yet */ |
|
memset(_tx[0], 0xF0, 16); |
|
memset(_tx[1], 0xF0, 4); |
|
memset(_purge, 0xFF, RNFD_BEBOP_NB_PULSES_PURGE); |
|
_tx_buf = _tx[_mode]; |
|
} |
|
|
|
AP_RangeFinder_Bebop::~AP_RangeFinder_Bebop() |
|
{ |
|
iio_buffer_destroy(_adc.buffer); |
|
_adc.buffer = nullptr; |
|
iio_context_destroy(_iio); |
|
_iio = nullptr; |
|
} |
|
|
|
bool AP_RangeFinder_Bebop::detect() |
|
{ |
|
return true; |
|
} |
|
|
|
unsigned short AP_RangeFinder_Bebop::get_threshold_at(int i_capture) |
|
{ |
|
uint16_t threshold_value = 0; |
|
|
|
/* |
|
* We define several kinds of thresholds signals ; for an echo to be |
|
* recorded, its maximum amplitude has to be ABOVE that threshold. |
|
* There is one kind of threshold per mode (mode 0 is "low" and mode 1 is |
|
* "high") |
|
* Basically they look like this : |
|
* |
|
* on this part |
|
* of the capture |
|
* amplitude we use |
|
* ^ the waveform |
|
* | <----------> |
|
* 4195 +-----+ |
|
* | |
|
* | |
|
* | |
|
* | |
|
* 1200| +----------------+ |
|
* +--------------------------------------> |
|
* + low high time |
|
* limit limit |
|
* |
|
* */ |
|
switch (_mode) { |
|
case 0: |
|
if (i_capture < 139) { |
|
threshold_value = 4195; |
|
] else if (i_capture < 153) { |
|
threshold_value = waveform_mode0[i_capture - 139]; |
|
} else { |
|
threshold_value = 1200; |
|
} |
|
break; |
|
|
|
case 1: |
|
if (i_capture < 73) { |
|
threshold_value = 4195; |
|
} else if (i_capture < 105) { |
|
threshold_value = waveform_mode1[i_capture - 73]; |
|
} else if (i_capture < 617) { |
|
threshold_value = 1200; |
|
} else { |
|
threshold_value = 4195; |
|
} |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
return threshold_value; |
|
} |
|
|
|
int AP_RangeFinder_Bebop::_apply_averaging_filter(void) |
|
{ |
|
|
|
int i_filter = 0; /* index in the filtered buffer */ |
|
int i_capture = 0; /* index in the capture buffer : starts incrementing when |
|
the captured data first exceeds |
|
RNFD_BEBOP_THRESHOLD_ECHO_INIT */ |
|
unsigned int filtered_value = 0; |
|
bool first_echo = false; |
|
unsigned char *data; |
|
unsigned char *start; |
|
unsigned char *end; |
|
ptrdiff_t step; |
|
|
|
step = iio_buffer_step(_adc.buffer); |
|
end = (unsigned char *) iio_buffer_end(_adc.buffer); |
|
start = (unsigned char *) iio_buffer_first(_adc.buffer, _adc.channel); |
|
|
|
for (data = start; data < end; data += step) { |
|
unsigned int current_value = 0; |
|
iio_channel_convert(_adc.channel, ¤t_value, data); |
|
|
|
/* We keep on advancing in the captured buffer without registering the |
|
* filtered data until the signal first exceeds a given value */ |
|
if (!first_echo && current_value < threshold_echo_init) { |
|
continue; |
|
} else { |
|
first_echo = true; |
|
} |
|
|
|
filtered_value += current_value; |
|
if (i_capture % _filter_average == 0) { |
|
_filtered_capture[i_filter] = filtered_value / _filter_average; |
|
filtered_value = 0; |
|
i_filter++; |
|
} |
|
i_capture++; |
|
} |
|
return 0; |
|
} |
|
|
|
int AP_RangeFinder_Bebop::_search_local_maxima(void) |
|
{ |
|
int i_echo = 0; /* index in echo array */ |
|
|
|
for (int i_capture = 1; i_capture < |
|
(int)_filtered_capture_size - 1; i_capture++) { |
|
if (_filtered_capture[i_capture] >= get_threshold_at(i_capture)) { |
|
unsigned short curr = _filtered_capture[i_capture]; |
|
unsigned short prev = _filtered_capture[i_capture - 1]; |
|
unsigned short next = _filtered_capture[i_capture + 1]; |
|
|
|
if (curr >= prev && (curr > next || prev < |
|
get_threshold_at(i_capture - 1))) { |
|
_echoes[i_echo].max_index = i_capture; |
|
i_echo++; |
|
if (i_echo >= RNFD_BEBOP_MAX_ECHOES) { |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
_nb_echoes = i_echo; |
|
return 0; |
|
} |
|
|
|
int AP_RangeFinder_Bebop::_search_maximum_with_max_amplitude(void) |
|
{ |
|
unsigned short max = 0; |
|
int max_idx = -1; |
|
|
|
for (int i_echo = 0; i_echo < _nb_echoes ; i_echo++) { |
|
unsigned short curr = _filtered_capture[_echoes[i_echo].max_index]; |
|
if (curr > max) { |
|
max = curr; |
|
max_idx = i_echo; |
|
} |
|
} |
|
|
|
if (max_idx >= 0) { |
|
return _echoes[max_idx].max_index; |
|
} else { |
|
return -1; |
|
} |
|
} |
|
|
|
void AP_RangeFinder_Bebop::_loop(void) |
|
{ |
|
int max_index; |
|
|
|
while(1) { |
|
_launch(); |
|
|
|
_capture(); |
|
|
|
if (_apply_averaging_filter() < 0) { |
|
hal.console->printf( |
|
"AR_RangeFinder_Bebop: could not apply averaging filter"); |
|
} |
|
|
|
if (_search_local_maxima() < 0) { |
|
hal.console->printf("Did not find any local maximum"); |
|
} |
|
|
|
max_index = _search_maximum_with_max_amplitude(); |
|
if (max_index >= 0) { |
|
_altitude = (float)(max_index * RNFD_BEBOP_SOUND_SPEED) / |
|
(2 * (RNFD_BEBOP_DEFAULT_ADC_FREQ / _filter_average)); |
|
} |
|
_mode = _update_mode(_altitude); |
|
} |
|
} |
|
|
|
void AP_RangeFinder_Bebop::update(void) |
|
{ |
|
static bool first_call = true; |
|
|
|
if (first_call) { |
|
_thread->start("RangeFinder_Bebop", SCHED_FIFO, 11); |
|
first_call = false; |
|
} |
|
|
|
state.distance_cm = (uint16_t) (_altitude * 100); |
|
state.last_reading_ms = AP_HAL::millis(); |
|
update_status(); |
|
} |
|
|
|
/* |
|
* purge is used when changing mode |
|
*/ |
|
int AP_RangeFinder_Bebop::_launch_purge() |
|
{ |
|
iio_device_attr_write(_adc.device, "buffer/enable", "1"); |
|
_spi->transfer(_purge, RNFD_BEBOP_NB_PULSES_PURGE, nullptr, 0); |
|
return 0; |
|
} |
|
|
|
void AP_RangeFinder_Bebop::_configure_gpio(int value) |
|
{ |
|
switch (value) { |
|
case 1: // high voltage |
|
_gpio->write(LINUX_GPIO_ULTRASOUND_VOLTAGE, 1); |
|
break; |
|
case 0: // low voltage |
|
_gpio->write(LINUX_GPIO_ULTRASOUND_VOLTAGE, 0); |
|
break; |
|
default: |
|
hal.console->printf("bad gpio value (%d)", value); |
|
break; |
|
} |
|
} |
|
|
|
/* |
|
* reconfigure the pulse that will be sent over spi |
|
* first send a purge then configure the new pulse |
|
*/ |
|
void AP_RangeFinder_Bebop::_reconfigure_wave() |
|
{ |
|
/* configure the output buffer for a purge */ |
|
/* perform a purge */ |
|
if (_launch_purge() < 0) { |
|
hal.console->printf("purge could not send data overspi"); |
|
} |
|
if (_capture() < 0) { |
|
hal.console->printf("purge could not capture data"); |
|
} |
|
|
|
_tx_buf = _tx[_mode]; |
|
switch (_mode) { |
|
case 1: /* low voltage */ |
|
_configure_gpio(0); |
|
break; |
|
case 0: /* high voltage */ |
|
_configure_gpio(1); |
|
break; |
|
default: |
|
hal.console->printf("WARNING, invalid value to configure gpio\n"); |
|
break; |
|
} |
|
} |
|
|
|
/* |
|
* First configuration of the the pulse that will be send over spi |
|
*/ |
|
int AP_RangeFinder_Bebop::_configure_wave() |
|
{ |
|
_spi->set_speed(AP_HAL::Device::SPEED_HIGH); |
|
_configure_gpio(0); |
|
return 0; |
|
} |
|
|
|
/* |
|
* Configure the adc to get the samples |
|
*/ |
|
int AP_RangeFinder_Bebop::_configure_capture() |
|
{ |
|
const char *adcname = "p7mu-adc_2"; |
|
const char *adcchannel = "voltage2"; |
|
/* configure adc interface using libiio */ |
|
_iio = iio_create_local_context(); |
|
if (!_iio) { |
|
return -1; |
|
} |
|
_adc.device = iio_context_find_device(_iio, adcname); |
|
|
|
if (!_adc.device) { |
|
hal.console->printf("Unable to find %s", adcname); |
|
goto error_destroy_context; |
|
} |
|
_adc.channel = iio_device_find_channel(_adc.device, adcchannel, |
|
false); |
|
if (!_adc.channel) { |
|
hal.console->printf("Fail to init adc channel %s", adcchannel); |
|
goto error_destroy_context; |
|
} |
|
|
|
iio_channel_enable(_adc.channel); |
|
|
|
_adc.freq = RNFD_BEBOP_DEFAULT_ADC_FREQ >> RNFD_BEBOP_FILTER_POWER; |
|
_adc.threshold_time_rejection = 2.0 / RNFD_BEBOP_SOUND_SPEED * |
|
_adc.freq; |
|
|
|
/* Create input buffer */ |
|
_adc.buffer_size = RNFD_BEBOP_P7_COUNT; |
|
if (iio_device_set_kernel_buffers_count(_adc.device, 1)) { |
|
hal.console->printf("cannot set buffer count"); |
|
goto error_destroy_context; |
|
} |
|
_adc.buffer = iio_device_create_buffer(_adc.device, |
|
_adc.buffer_size, false); |
|
if (!_adc.buffer) { |
|
hal.console->printf("Fail to create buffer : %s", strerror(errno)); |
|
goto error_destroy_context; |
|
} |
|
|
|
return 0; |
|
|
|
error_destroy_context: |
|
iio_buffer_destroy(_adc.buffer); |
|
_adc.buffer = nullptr; |
|
iio_context_destroy(_iio); |
|
_iio = nullptr; |
|
return -1; |
|
} |
|
|
|
void AP_RangeFinder_Bebop::_init() |
|
{ |
|
_spi = std::move(hal.spi->get_device("bebop")); |
|
|
|
_gpio = AP_HAL::get_HAL().gpio; |
|
if (_gpio == nullptr) { |
|
AP_HAL::panic("Could not find GPIO device for Bebop ultrasound"); |
|
} |
|
|
|
if (_configure_capture() < 0) { |
|
return; |
|
} |
|
|
|
_configure_wave(); |
|
|
|
return; |
|
} |
|
|
|
/* |
|
* enable the capture buffer |
|
* send a pulse over spi |
|
*/ |
|
int AP_RangeFinder_Bebop::_launch() |
|
{ |
|
iio_device_attr_write(_adc.device, "buffer/enable", "1"); |
|
_spi->transfer(_tx_buf, RNFD_BEBOP_NB_PULSES_MAX, nullptr, 0); |
|
return 0; |
|
} |
|
|
|
/* |
|
* read the iio buffer |
|
* iio_buffer_refill is blocking by default, so this function is also |
|
* blocking until samples are available |
|
* disable the capture buffer |
|
*/ |
|
int AP_RangeFinder_Bebop::_capture() |
|
{ |
|
int ret; |
|
|
|
ret = iio_buffer_refill(_adc.buffer); |
|
iio_device_attr_write(_adc.device, "buffer/enable", "0"); |
|
return ret; |
|
} |
|
|
|
int AP_RangeFinder_Bebop::_update_mode(float altitude) |
|
{ |
|
switch (_mode) { |
|
case 0: |
|
if (altitude < RNFD_BEBOP_TRANSITION_HIGH_TO_LOW |
|
&& !is_zero(altitude)) { |
|
if (_hysteresis_counter > RNFD_BEBOP_TRANSITION_COUNT) { |
|
_mode = 1; |
|
_hysteresis_counter = 0; |
|
_reconfigure_wave(); |
|
} else { |
|
_hysteresis_counter++; |
|
} |
|
} else { |
|
_hysteresis_counter = 0; |
|
} |
|
break; |
|
|
|
default: |
|
case 1: |
|
if (altitude > RNFD_BEBOP_TRANSITION_LOW_TO_HIGH |
|
|| is_zero(altitude)) { |
|
if (_hysteresis_counter > RNFD_BEBOP_TRANSITION_COUNT) { |
|
_mode = 0; |
|
_hysteresis_counter = 0; |
|
_reconfigure_wave(); |
|
} else { |
|
_hysteresis_counter++; |
|
} |
|
} else { |
|
_hysteresis_counter = 0; |
|
} |
|
break; |
|
} |
|
return _mode; |
|
} |
|
#endif
|
|
|