/*
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 .
*/
#include
#include
#if (CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_BEBOP || \
CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_LINUX_DISCO) && \
defined(HAVE_LIBIIO)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "AP_RangeFinder_Bebop.h"
#include
#include
/*
* 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