From 7ff00db9c59cd7a3d319bd66cf41420f0a9189a6 Mon Sep 17 00:00:00 2001 From: AlexKlimaj Date: Wed, 5 Apr 2023 11:21:24 -0600 Subject: [PATCH] Update AFBR to 1.4.4. Remove changing measurement modes. Add parameters. Default to Long range mode" --- .../broadcom/afbrs50/AFBRS50.cpp | 223 +- .../broadcom/afbrs50/AFBRS50.hpp | 29 +- .../broadcom/afbrs50/API/Src/s2pi.c | 39 +- .../broadcom/afbrs50/Inc/api/argus_api.h | 1711 +++++++------ .../broadcom/afbrs50/Inc/api/argus_dca.h | 259 +- .../broadcom/afbrs50/Inc/api/argus_def.h | 216 +- .../broadcom/afbrs50/Inc/api/argus_dfm.h | 40 +- .../broadcom/afbrs50/Inc/api/argus_map.h | 382 ++- .../broadcom/afbrs50/Inc/api/argus_meas.h | 39 +- .../broadcom/afbrs50/Inc/api/argus_offset.h | 59 + .../broadcom/afbrs50/Inc/api/argus_pba.h | 212 +- .../broadcom/afbrs50/Inc/api/argus_px.h | 99 +- .../broadcom/afbrs50/Inc/api/argus_res.h | 144 +- .../broadcom/afbrs50/Inc/api/argus_snm.h | 40 +- .../broadcom/afbrs50/Inc/api/argus_status.h | 55 +- .../broadcom/afbrs50/Inc/api/argus_version.h | 30 +- .../broadcom/afbrs50/Inc/api/argus_xtalk.h | 62 +- .../broadcom/afbrs50/Inc/argus.h | 10 +- .../broadcom/afbrs50/Inc/platform/argus_irq.h | 122 +- .../broadcom/afbrs50/Inc/platform/argus_nvm.h | 150 +- .../afbrs50/Inc/platform/argus_print.h | 59 +- .../afbrs50/Inc/platform/argus_s2pi.h | 452 ++-- .../afbrs50/Inc/platform/argus_timer.h | 245 +- .../broadcom/afbrs50/Inc/utility/fp_def.h | 274 +- .../broadcom/afbrs50/Inc/utility/fp_div.h | 173 ++ .../broadcom/afbrs50/Inc/utility/fp_ema.h | 204 ++ .../broadcom/afbrs50/Inc/utility/fp_exp.h | 69 + .../broadcom/afbrs50/Inc/utility/fp_log.h | 69 + .../broadcom/afbrs50/Inc/utility/fp_mul.h | 235 ++ .../broadcom/afbrs50/Inc/utility/fp_rnd.h | 118 + .../broadcom/afbrs50/Inc/utility/int_math.h | 281 +++ .../broadcom/afbrs50/Inc/utility/status.h | 49 + .../broadcom/afbrs50/Inc/utility/time.h | 618 +++-- .../broadcom/afbrs50/Lib/libafbrs50_m4_fpu.a | Bin 222646 -> 231568 bytes .../afbrs50/Lib/libafbrs50_m4_fpu_os.a | Bin 186938 -> 183564 bytes .../broadcom/afbrs50/argus_hal_test.c | 2205 ++++++++++++----- .../broadcom/afbrs50/argus_hal_test.h | 251 +- .../broadcom/afbrs50/parameters.c | 102 + src/drivers/uavcan/sensors/rangefinder.cpp | 15 +- 39 files changed, 6383 insertions(+), 2957 deletions(-) create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_offset.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_div.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_ema.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_exp.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_log.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_mul.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_rnd.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/int_math.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/status.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/parameters.c diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.cpp b/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.cpp index b66eeeba3d..4419530515 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.cpp +++ b/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.cpp @@ -33,6 +33,7 @@ /* Include Files */ #include "AFBRS50.hpp" +#include "argus_hal_test.h" #include @@ -42,9 +43,6 @@ /*! Define the SPI baud rate (to be used in the SPI module). */ #define SPI_BAUD_RATE 5000000 -#define LONG_RANGE_MODE_HZ 25 -#define SHORT_RANGE_MODE_HZ 50 - #include "s2pi.h" #include "timer.h" #include "argus_hal_test.h" @@ -52,6 +50,7 @@ AFBRS50 *g_dev{nullptr}; AFBRS50::AFBRS50(uint8_t device_orientation): + ModuleParams(nullptr), ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::hp_default), _px4_rangefinder(0, device_orientation) { @@ -61,6 +60,7 @@ AFBRS50::AFBRS50(uint8_t device_orientation): device_id.devid_s.devtype = DRV_DIST_DEVTYPE_AFBRS50; _px4_rangefinder.set_device_id(device_id.devid); + _px4_rangefinder.set_device_type(distance_sensor_s::MAV_DISTANCE_SENSOR_LASER); } AFBRS50::~AFBRS50() @@ -70,12 +70,12 @@ AFBRS50::~AFBRS50() perf_free(_sample_perf); } -status_t AFBRS50::measurement_ready_callback(status_t status, void *data) +status_t AFBRS50::measurement_ready_callback(status_t status, argus_hnd_t *hnd) { if (!up_interrupt_context()) { if (status == STATUS_OK) { if (g_dev) { - g_dev->ProcessMeasurement(data); + g_dev->ProcessMeasurement(hnd); } } else { @@ -86,35 +86,33 @@ status_t AFBRS50::measurement_ready_callback(status_t status, void *data) return status; } -void AFBRS50::ProcessMeasurement(void *data) +void AFBRS50::ProcessMeasurement(argus_hnd_t *hnd) { - if (data != nullptr) { - perf_count(_sample_perf); + perf_count(_sample_perf); - argus_results_t res{}; - status_t evaluate_status = Argus_EvaluateData(_hnd, &res, data); + argus_results_t res{}; + status_t evaluate_status = Argus_EvaluateData(hnd, &res); - if ((evaluate_status == STATUS_OK) && (res.Status == STATUS_OK)) { - uint32_t result_mm = res.Bin.Range / (Q9_22_ONE / 1000); - float result_m = static_cast(result_mm) / 1000.f; - int8_t quality = res.Bin.SignalQuality; + if ((evaluate_status == STATUS_OK) && (res.Status == STATUS_OK)) { + uint32_t result_mm = res.Bin.Range / (Q9_22_ONE / 1000); + float result_m = static_cast(result_mm) / 1000.f; + int8_t quality = res.Bin.SignalQuality; - // Signal quality indicates 100% for good signals, 50% and lower for weak signals. - // 1% is an errored signal (not reliable). Signal Quality of 0% is unknown. - if (quality == 1) { - quality = 0; - } - - // distance quality check - if (result_m > _max_distance) { - result_m = 0.0; - quality = 0; - } - - _current_distance = result_m; - _current_quality = quality; - _px4_rangefinder.update(((res.TimeStamp.sec * 1000000ULL) + res.TimeStamp.usec), result_m, quality); + // Signal quality indicates 100% for good signals, 50% and lower for weak signals. + // 1% is an errored signal (not reliable). Signal Quality of 0% is unknown. + if (quality == 1) { + quality = 0; } + + // distance quality check + if (result_m > _max_distance) { + result_m = 0.0; + quality = 0; + } + + _current_distance = result_m; + _current_quality = quality; + _px4_rangefinder.update(((res.TimeStamp.sec * 1000000ULL) + res.TimeStamp.usec), result_m, quality); } } @@ -137,7 +135,37 @@ int AFBRS50::init() // Initialize the S2PI hardware required by the API. S2PI_Init(BROADCOM_AFBR_S50_S2PI_SPI_BUS, SPI_BAUD_RATE); - status_t status = Argus_Init(_hnd, BROADCOM_AFBR_S50_S2PI_SPI_BUS); + int32_t mode_param = _p_sens_afbr_mode.get(); + + if (mode_param < 0 || mode_param > 3) { + PX4_ERR("Invalid mode parameter: %li", mode_param); + return PX4_ERROR; + } + + argus_mode_t mode = ARGUS_MODE_LONG_RANGE; + + switch (mode_param) { + case 0: + mode = ARGUS_MODE_SHORT_RANGE; + break; + + case 1: + mode = ARGUS_MODE_LONG_RANGE; + break; + + case 2: + mode = ARGUS_MODE_HIGH_SPEED_SHORT_RANGE; + break; + + case 3: + mode = ARGUS_MODE_HIGH_SPEED_LONG_RANGE; + break; + + default: + break; + } + + status_t status = Argus_InitMode(_hnd, BROADCOM_AFBR_S50_S2PI_SPI_BUS, mode); if (status == STATUS_OK) { uint32_t id = Argus_GetChipID(_hnd); @@ -148,7 +176,6 @@ int AFBRS50::init() PX4_INFO_RAW("AFBR-S50 Chip ID: %u, API Version: %u v%d.%d.%d\n", (uint)id, (uint)value, a, b, c); argus_module_version_t mv = Argus_GetModuleVersion(_hnd); - argus_laser_type_t lt = Argus_GetLaserType(_hnd); switch (mv) { case AFBR_S50MV85G_V1: @@ -168,19 +195,20 @@ int AFBRS50::init() case AFBR_S50LV85D_V1: _min_distance = 0.08f; - - if (lt == LASER_H_V2X) { - _max_distance = 50.f; - PX4_INFO_RAW("AFBR-S50LX85D (v2)\n"); - - } else { - _max_distance = 30.f; - PX4_INFO_RAW("AFBR-S50LV85D (v1)\n"); - } - + _max_distance = 30.f; _px4_rangefinder.set_min_distance(_min_distance); _px4_rangefinder.set_max_distance(_max_distance); _px4_rangefinder.set_fov(math::radians(6.f)); + PX4_INFO_RAW("AFBR-S50LV85D\n"); + break; + + case AFBR_S50LX85D_V1: + _min_distance = 0.08f; + _max_distance = 50.f; + _px4_rangefinder.set_min_distance(_min_distance); + _px4_rangefinder.set_max_distance(_max_distance); + _px4_rangefinder.set_fov(math::radians(6.f)); + PX4_INFO_RAW("AFBR-S50LX85D\n"); break; case AFBR_S50MV68B_V1: @@ -223,6 +251,9 @@ int AFBRS50::init() ScheduleDelayed(_measure_interval); return PX4_OK; + + } else { + PX4_ERR("Argus_InitMode failed: %ld", status); } return PX4_ERROR; @@ -230,6 +261,15 @@ int AFBRS50::init() void AFBRS50::Run() { + if (_parameter_update_sub.updated()) { + // clear update + parameter_update_s param_update; + _parameter_update_sub.copy(¶m_update); + + // update parameters from storage + ModuleParams::updateParams(); + } + switch (_state) { case STATE::TEST: { if (_testing) { @@ -243,7 +283,8 @@ void AFBRS50::Run() break; case STATE::CONFIGURE: { - status_t status = set_rate(SHORT_RANGE_MODE_HZ); + _current_rate = (uint32_t)_p_sens_afbr_s_rate.get(); + status_t status = set_rate(_current_rate); if (status != STATUS_OK) { PX4_ERR("CONFIGURE status not okay: %i", (int)status); @@ -251,24 +292,18 @@ void AFBRS50::Run() ScheduleNow(); } - status = Argus_SetConfigurationDFMMode(_hnd, ARGUS_MODE_B, DFM_MODE_8X); + status = Argus_SetConfigurationDFMMode(_hnd, DFM_MODE_8X); if (status != STATUS_OK) { PX4_ERR("Argus_SetConfigurationDFMMode status not okay: %i", (int)status); + _state = STATE::STOP; + ScheduleNow(); } - status = Argus_SetConfigurationDFMMode(_hnd, ARGUS_MODE_A, DFM_MODE_8X); + status = Argus_SetConfigurationSmartPowerSaveEnabled(_hnd, false); if (status != STATUS_OK) { - PX4_ERR("Argus_SetConfigurationDFMMode status not okay: %i", (int)status); - } - - // start in short range mode - _mode = ARGUS_MODE_B; - set_mode(_mode); - - if (status != STATUS_OK) { - PX4_ERR("CONFIGURE status not okay: %i", (int)status); + PX4_ERR("Argus_SetConfigurationSmartPowerSaveEnabled status not okay: %i", (int)status); ScheduleNow(); } else { @@ -288,7 +323,7 @@ void AFBRS50::Run() } } - UpdateMode(); + Evaluate_rate(); } break; @@ -306,49 +341,41 @@ void AFBRS50::Run() ScheduleDelayed(_measure_interval); } -void AFBRS50::UpdateMode() +void AFBRS50::Evaluate_rate() { - // only update mode if _current_distance is a valid measurement - if ((_current_distance > 0) && (_current_quality > 0)) { + // only update mode if _current_distance is a valid measurement and if the last rate switch was more than 1 second ago + if ((_current_distance > 0) && (_current_quality > 0) && ((hrt_absolute_time() - _last_rate_switch) > 1_s)) { - if ((_current_distance >= _long_range_threshold) && (_mode != ARGUS_MODE_A)) { - // change to long range mode - argus_mode_t mode = ARGUS_MODE_A; - status_t status = set_mode(mode); + status_t status = STATUS_OK; - if (status != STATUS_OK) { - PX4_ERR("set_mode status not okay: %i", (int)status); - } + if ((_current_distance >= (_p_sens_afbr_thresh.get() + _p_sens_afbr_hyster.get())) + && (_current_rate != (uint32_t)_p_sens_afbr_l_rate.get())) { - status = set_rate(LONG_RANGE_MODE_HZ); + _current_rate = (uint32_t)_p_sens_afbr_l_rate.get(); + status = set_rate(_current_rate); if (status != STATUS_OK) { PX4_ERR("set_rate status not okay: %i", (int)status); + + } else { + PX4_INFO("switched to long range rate: %i", (int)_current_rate); + _last_rate_switch = hrt_absolute_time(); } - status = set_rate(LONG_RANGE_MODE_HZ); + } else if ((_current_distance <= (_p_sens_afbr_thresh.get() - _p_sens_afbr_hyster.get())) + && (_current_rate != (uint32_t)_p_sens_afbr_s_rate.get())) { + + _current_rate = (uint32_t)_p_sens_afbr_s_rate.get(); + status = set_rate(_current_rate); if (status != STATUS_OK) { PX4_ERR("set_rate status not okay: %i", (int)status); - } - } else if ((_current_distance <= _short_range_threshold) && (_mode != ARGUS_MODE_B)) { - // change to short range mode - argus_mode_t mode = ARGUS_MODE_B; - status_t status = set_mode(mode); - - if (status != STATUS_OK) { - PX4_ERR("set_mode status not okay: %i", (int)status); - } - - status = set_rate(SHORT_RANGE_MODE_HZ); - - if (status != STATUS_OK) { - PX4_ERR("set_rate status not okay: %i", (int)status); + } else { + PX4_INFO("switched to short range rate: %i", (int)_current_rate); + _last_rate_switch = hrt_absolute_time(); } } - - ScheduleDelayed(1000_ms); // don't switch again for at least 1 second } } @@ -373,33 +400,6 @@ void AFBRS50::print_info() get_info(); } -status_t AFBRS50::set_mode(argus_mode_t mode) -{ - while (Argus_GetStatus(_hnd) != STATUS_IDLE) { - px4_usleep(1_ms); - } - - status_t status = Argus_SetConfigurationMeasurementMode(_hnd, mode); - - if (status != STATUS_OK) { - PX4_ERR("Argus_SetConfigurationMeasurementMode status not okay: %i", (int)status); - return status; - } - - argus_mode_t current_mode; - status = Argus_GetConfigurationMeasurementMode(_hnd, ¤t_mode); - - if (status != STATUS_OK) { - PX4_ERR("Argus_GetConfigurationMeasurementMode status not okay: %i", (int)status); - return status; - - } else { - _mode = current_mode; - } - - return status; -} - status_t AFBRS50::set_rate(uint32_t rate_hz) { while (Argus_GetStatus(_hnd) != STATUS_IDLE) { @@ -429,13 +429,10 @@ status_t AFBRS50::set_rate(uint32_t rate_hz) void AFBRS50::get_info() { - argus_mode_t current_mode; argus_dfm_mode_t dfm_mode; - Argus_GetConfigurationMeasurementMode(_hnd, ¤t_mode); - Argus_GetConfigurationDFMMode(_hnd, current_mode, &dfm_mode); + Argus_GetConfigurationDFMMode(_hnd, &dfm_mode); PX4_INFO_RAW("distance: %.3fm\n", (double)_current_distance); - PX4_INFO_RAW("mode: %d\n", current_mode); PX4_INFO_RAW("dfm mode: %d\n", dfm_mode); PX4_INFO_RAW("rate: %u Hz\n", (uint)(1000000 / _measure_interval)); } diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.hpp b/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.hpp index f7503b321b..2ad767b2fa 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.hpp +++ b/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.hpp @@ -44,13 +44,16 @@ #include #include #include -#include #include +#include +#include #include +#include +#include using namespace time_literals; -class AFBRS50 : public px4::ScheduledWorkItem +class AFBRS50 : public ModuleParams, public px4::ScheduledWorkItem { public: AFBRS50(const uint8_t device_orientation = distance_sensor_s::ROTATION_DOWNWARD_FACING); @@ -75,18 +78,16 @@ public: private: void Run() override; - void UpdateMode(); + void Evaluate_rate(); - void ProcessMeasurement(void *data); + void ProcessMeasurement(argus_hnd_t *hnd); - static status_t measurement_ready_callback(status_t status, void *data); + static status_t measurement_ready_callback(status_t status, argus_hnd_t *hnd); void get_info(); - status_t set_mode(argus_mode_t mode); status_t set_rate(uint32_t rate_hz); argus_hnd_t *_hnd{nullptr}; - argus_mode_t _mode{ARGUS_MODE_B}; // Short-Range enum class STATE : uint8_t { TEST, @@ -98,14 +99,24 @@ private: PX4Rangefinder _px4_rangefinder; hrt_abstime _measurement_time{0}; + hrt_abstime _last_rate_switch{0}; perf_counter_t _sample_perf{perf_alloc(PC_INTERVAL, MODULE_NAME": sample interval")}; uint32_t _measure_interval{1000000 / 50}; // 50Hz float _current_distance{0}; int8_t _current_quality{0}; - const float _short_range_threshold = 4.0; //meters - const float _long_range_threshold = 6.0; //meters float _max_distance; float _min_distance; + uint32_t _current_rate{0}; + + uORB::Subscription _parameter_update_sub{ORB_ID(parameter_update)}; + + DEFINE_PARAMETERS( + (ParamInt) _p_sens_afbr_mode, + (ParamInt) _p_sens_afbr_s_rate, + (ParamInt) _p_sens_afbr_l_rate, + (ParamInt) _p_sens_afbr_thresh, + (ParamInt) _p_sens_afbr_hyster + ); }; diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/s2pi.c b/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/s2pi.c index 61950a12b8..015249f859 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/s2pi.c +++ b/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/s2pi.c @@ -85,6 +85,8 @@ static int gpio_falling_edge(int irq, void *context, void *arg) status_t S2PI_Init(s2pi_slave_t defaultSlave, uint32_t baudRate_Bps) { + (void)defaultSlave; + px4_arch_configgpio(BROADCOM_AFBR_S50_S2PI_CS); s2pi_.spidev = px4_spibus_initialize(BROADCOM_AFBR_S50_S2PI_SPI_BUS); @@ -107,11 +109,24 @@ status_t S2PI_Init(s2pi_slave_t defaultSlave, uint32_t baudRate_Bps) * - #STATUS_BUSY: An SPI transfer is in progress. * - #STATUS_S2PI_GPIO_MODE: The module is in GPIO mode. *****************************************************************************/ -status_t S2PI_GetStatus(void) +status_t S2PI_GetStatus(s2pi_slave_t slave) { + (void)slave; + return s2pi_.Status; } +status_t S2PI_TryGetMutex(s2pi_slave_t slave) +{ + (void) slave; + return STATUS_OK; +} + +void S2PI_ReleaseMutex(s2pi_slave_t slave) +{ + (void) slave; +} + /*!*************************************************************************** * @brief Sets the SPI baud rate in bps. * @param baudRate_Bps The default SPI baud rate in bauds-per-second. @@ -135,8 +150,10 @@ status_t S2PI_SetBaudRate(uint32_t baudRate_Bps) * switch back to ordinary SPI functionality. * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t S2PI_CaptureGpioControl(void) +status_t S2PI_CaptureGpioControl(s2pi_slave_t slave) { + (void)slave; + /* Check if something is ongoing. */ IRQ_LOCK(); status_t status = s2pi_.Status; @@ -165,8 +182,10 @@ status_t S2PI_CaptureGpioControl(void) * the #S2PI_CaptureGpioControl function. * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t S2PI_ReleaseGpioControl(void) +status_t S2PI_ReleaseGpioControl(s2pi_slave_t slave) { + (void)slave; + /* Check if something is ongoing. */ IRQ_LOCK(); status_t status = s2pi_.Status; @@ -202,6 +221,8 @@ status_t S2PI_ReleaseGpioControl(void) *****************************************************************************/ status_t S2PI_WriteGpioPin(s2pi_slave_t slave, s2pi_pin_t pin, uint32_t value) { + (void)slave; + /* Check if pin is valid. */ if (pin > S2PI_IRQ || value > 1) { return ERROR_INVALID_ARGUMENT; @@ -228,6 +249,8 @@ status_t S2PI_WriteGpioPin(s2pi_slave_t slave, s2pi_pin_t pin, uint32_t value) *****************************************************************************/ status_t S2PI_ReadGpioPin(s2pi_slave_t slave, s2pi_pin_t pin, uint32_t *value) { + (void)slave; + /* Check if pin is valid. */ if (pin > S2PI_IRQ || !value) { return ERROR_INVALID_ARGUMENT; @@ -255,6 +278,8 @@ status_t S2PI_ReadGpioPin(s2pi_slave_t slave, s2pi_pin_t pin, uint32_t *value) *****************************************************************************/ status_t S2PI_CycleCsPin(s2pi_slave_t slave) { + (void)slave; + /* Check the driver status. */ IRQ_LOCK(); status_t status = s2pi_.Status; @@ -372,8 +397,10 @@ status_t S2PI_TransferFrame(s2pi_slave_t spi_slave, uint8_t const *txData, uint8 * invoked with the #ERROR_ABORTED error byte. * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t S2PI_Abort(void) +status_t S2PI_Abort(s2pi_slave_t slave) { + (void)slave; + status_t status = s2pi_.Status; /* Check if something is ongoing. */ @@ -405,6 +432,8 @@ status_t S2PI_Abort(void) *****************************************************************************/ status_t S2PI_SetIrqCallback(s2pi_slave_t slave, s2pi_irq_callback_t callback, void *callbackData) { + (void)slave; + s2pi_.IrqCallback = callback; s2pi_.IrqCallbackData = callbackData; @@ -430,5 +459,7 @@ status_t S2PI_SetIrqCallback(s2pi_slave_t slave, s2pi_irq_callback_t callback, v *****************************************************************************/ uint32_t S2PI_ReadIrqPin(s2pi_slave_t slave) { + (void)slave; + return px4_arch_gpioread(s2pi_.GPIOs[S2PI_IRQ]); } diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_api.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_api.h index a933976370..44cdc92168 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_api.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_api.h @@ -1,12 +1,12 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file provides generic functionality belonging to all - * devices from the AFBR-S50 product family. + * @brief This file is part of the AFBR-S50 API. + * @details This file provides generic functionality belonging to all + * devices from the AFBR-S50 product family. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,21 +37,21 @@ #ifndef ARGUS_API_H #define ARGUS_API_H - #ifdef __cplusplus extern "C" { #endif /*!*************************************************************************** - * @defgroup argusapi AFBR-S50 API + * @defgroup argus_api AFBR-S50 API + * @ingroup argus * - * @brief The main module of the API from the AFBR-S50 SDK. + * @brief The main module of the API from the AFBR-S50 SDK. * - * @details General API for the AFBR-S50 time-of-flight sensor device family.\n - * See the \ref getting_started Guide for a detailed description - * on how to use the module/API. + * @details General API for the AFBR-S50 time-of-flight sensor device family.\n + * See the \ref getting_started Guide for a detailed description + * on how to use the module/API. * - * @addtogroup argusapi + * @addtogroup argus_api * @{ *****************************************************************************/ @@ -61,213 +61,301 @@ extern "C" { #include "argus_dfm.h" #include "argus_snm.h" #include "argus_xtalk.h" - -/*! The data structure for the API representing a AFBR-S50 device instance. */ -typedef void argus_hnd_t; +#include "argus_offset.h" /*! The S2PI slave identifier. */ typedef int32_t s2pi_slave_t; /*!*************************************************************************** - * @brief Initializes the API modules and the device with default parameters. + * @brief Initializes the device with default measurement mode. * * @details The function that needs to be called once after power up to - * initialize the modules state (i.e. the corresponding handle) and the - * dedicated Time-of-Flight device. In order to obtain a handle, - * reference the #Argus_CreateHandle method. + * initialize the modules state (i.e. the corresponding handle) and the + * dedicated Time-of-Flight device. In order to obtain a handle, + * reference the #Argus_CreateHandle method. * - * Prior to calling the function, the required peripherals (i.e. S2PI, - * GPIO w/ IRQ and Timers) must be initialized and ready to use. + * Prior to calling the function, the required peripherals (i.e. S2PI, + * GPIO w/ IRQ and Timers) must be initialized and ready to use. * - * The function executes the following tasks: - * - Initialization of the internal state represented by the handle - * object. - * - Setup the device such that an safe configuration is present in - * the registers. - * - Initialize sub modules such as calibration or measurement modules. - * . + * The function executes the following tasks: + * - Initialization of the internal state represented by the handle + * object. + * - Setup the device such that an safe configuration is present in + * the registers. + * - Initialize sub modules such as calibration or measurement modules. + * . * - * The modules configuration is initialized with reasonable default values. + * The modules configuration is initialized with reasonable default + * values. Note that the default measurement mode depends on the + * given device. * - * @param hnd The API handle; contains all internal states and data. + * Also refer to #Argus_InitMode, which uses an specified measurement + * mode instead of the dedicated default measurement mode. * - * @param spi_slave The SPI hardware slave, i.e. the specified CS and IRQ - * lines. This is actually just a number that is passed - * to the SPI interface to distinct for multiple SPI slave - * devices. Note that the slave must be not equal to 0, - * since is reserved for error handling. + * @param hnd The API handle; contains all internal states and data. * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param spi_slave The SPI hardware slave, i.e. the specified CS and IRQ + * lines. This is actually just a number that is passed + * to the SPI interface to distinct for multiple SPI slave + * devices. Note that the slave must be not equal to 0, + * since is reserved for error handling. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_Init(argus_hnd_t *hnd, s2pi_slave_t spi_slave); /*!*************************************************************************** - * @brief Reinitializes the API modules and the device with default parameters. + * @brief Initializes the device with specified measurement mode. * - * @details The function reinitializes the device with default configuration. - * Can be used as reset sequence for the device. See #Argus_Init for - * more information on the initialization. + * @details The function that needs to be called once after power up to + * initialize the modules state (i.e. the corresponding handle) and the + * dedicated Time-of-Flight device. In order to obtain a handle, + * reference the #Argus_CreateHandle method. * - * Note that the #Argus_Init function must be called first! Otherwise, - * the function will return an error if it is called for an yet - * uninitialized device/handle. + * Prior to calling the function, the required peripherals (i.e. S2PI, + * GPIO w/ IRQ and Timers) must be initialized and ready to use. * - * @param hnd The API handle; contains all internal states and data. + * The function executes the following tasks: + * - Initialization of the internal state represented by the handle + * object. + * - Setup the device such that an safe configuration is present in + * the registers. + * - Initialize sub modules such as calibration or measurement modules. + * . * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * The modules configuration is initialized with reasonable default values. + * + * Also refer to #Argus_Init, which uses the dedicated default measurement + * mode instead of an user specified measurement mode. + * + * @param hnd The API handle; contains all internal states and data. + * + * @param spi_slave The SPI hardware slave, i.e. the specified CS and IRQ + * lines. This is actually just a number that is passed + * to the SPI interface to distinct for multiple SPI slave + * devices. Note that the slave must be not equal to 0, + * since is reserved for error handling. + * + * @param mode The specified measurement mode to be initialized. + * Pass 0 as special value to select default measurement mode + * (see #Argus_Init). + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_InitMode(argus_hnd_t *hnd, s2pi_slave_t spi_slave, argus_mode_t mode); + +/*!*************************************************************************** + * @brief Reinitializes the device with the current measurement mode. + * + * @details The function reinitializes the device with the currently active + * measurement mode. + * + * This can be used as a soft reset for the device and API. + * See #Argus_Init for more information on the initialization. + * + * Note that the #Argus_Init or #Argus_InitMode function must be called + * first! Otherwise, the function will return an error if it is called + * for an yet uninitialized device/handle. + * + * Also refer to #Argus_ReinitMode, which uses a specified measurement + * mode instead of the currently active measurement mode. + * + * @param hnd The API handle; contains all internal states and data. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_Reinit(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Deinitializes the API modules and the device. + * @brief Reinitializes the device with a specified measurement mode. + * + * @details The function reinitializes the device with a specified (/p mode) + * measurement mode. + * + * This can be used as a soft reset for the device and API. + * See #Argus_InitMode for more information on the initialization. + * + * Note that the #Argus_Init or #Argus_InitMode function must be called + * first! Otherwise, the function will return an error if it is called + * for an yet uninitialized device/handle. + * + * Also refer to #Argus_Reinit, which re-uses the currently active + * measurement mode instead of an user specified measurement mode. + * + * @param hnd The API handle; contains all internal states and data. + * + * @param mode The specified measurement mode to be initialized. + * Pass 0 as special value to select the current measurement mode + * (see #Argus_Init). + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_ReinitMode(argus_hnd_t *hnd, argus_mode_t mode); + +/*!*************************************************************************** + * @brief Deinitializes the API modules and the device. * * @details The function deinitializes the device and clear all internal states. - * Can be used to cleanup before releasing the memory. The device - * can not be used any more and must be initialized again prior to next - * usage. + * Can be used to cleanup before releasing the memory. The device + * can not be used any more and must be initialized again prior to next + * usage. * - * Note that the #Argus_Init function must be called first! Otherwise, - * the function will return an error if it is called for an yet - * uninitialized device/handle. + * Note that the #Argus_Init function must be called first! Otherwise, + * the function will return an error if it is called for an yet + * uninitialized device/handle. * - * @param hnd The API handle; contains all internal states and data. + * @param hnd The API handle; contains all internal states and data. * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_Deinit(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Creates a new device data handle object to store all internal states. + * @brief Creates a new device data handle object to store all internal states. * * @details The function must be called to obtain a new device handle object. - * The handle is basically an abstract object in memory that contains - * all the internal states and settings of the API module. The handle - * is passed to all the API methods in order to address the specified - * device. This allows to use the API with more than a single measurement - * device. + * The handle is basically an abstract object in memory that contains + * all the internal states and settings of the API module. The handle + * is passed to all the API methods in order to address the specified + * device. This allows to use the API with more than a single measurement + * device. * - * The handler is created by calling the memory allocation method from - * the standard library: @code void * malloc(size_t size) @endcode - * In order to implement an individual memory allocation method, - * define and implement the following weakly binded method and return - * a pointer to the newly allocated memory. * + * The handler is created by calling the memory allocation method from + * the standard library: @code void * malloc(size_t size) @endcode + * In order to implement an individual memory allocation method, + * define and implement the following weakly binded method and return + * a pointer to the newly allocated memory. * * - * @code void * Argus_Malloc (size_t size) @endcode + * @code void * Argus_Malloc (size_t size) @endcode * - * Also see the #Argus_DestroyHandle method for the corresponding - * deallocation of the allocated memory. + * Also see the #Argus_DestroyHandle method for the corresponding + * deallocation of the allocated memory. * - * @note Although the method is using memory allocated on the heap, it - * is eventually no dynamic memory allocation, since the block of - * memory is kept all the time and no memory blocks are dynamically - * freed and re-allocated. If the usage of heap must be avoided, one - * can always implement its own version of the `Argus_Malloc` function - * to create the memory elsewhere. + * @note Although the method is using memory allocated on the heap, it + * is eventually no dynamic memory allocation, since the block of + * memory is kept all the time and no memory blocks are dynamically + * freed and re-allocated. If the usage of heap must be avoided, one + * can always implement its own version of the `Argus_Malloc` function + * to create the memory elsewhere. * - * @return Returns a pointer to the newly allocated device handler object. - * Returns a null pointer if the allocation failed! + * @return Returns a pointer to the newly allocated device handler object. + * Returns a null pointer if the allocation failed! *****************************************************************************/ argus_hnd_t *Argus_CreateHandle(void); /*!*************************************************************************** - * @brief Destroys a given device data handle object. + * @brief Destroys a given device data handle object. * * @details The function can be called to free the previously created device - * data handle object in order to save memory when the device is not - * used any more. + * data handle object in order to save memory when the device is not + * used any more. * - * Please refer to the #Argus_CreateHandle method for the corresponding - * allocation of the memory. + * Note that the handle must be deinitialized before it can be + * destroyed. The function returns #ERROR_FAIL if the handle is not + * yet deinitialized. * - * The handler is destroyed by freeing the corresponding memory with the - * method from the standard library, @code void free(void * ptr) @endcode. - * In order to implement an individual memory deallocation method, define - * and implement the following weakly binded method and free the memory - * object passed to the method by a pointer. + * Please refer to the #Argus_CreateHandle method for the corresponding + * allocation of the memory. * - * @code void Argus_Free (void * ptr) @endcode + * The handler is destroyed by freeing the corresponding memory with the + * method from the standard library, @code void free(void * ptr) @endcode. + * In order to implement an individual memory deallocation method, define + * and implement the following weakly binded method and free the memory + * object passed to the method by a pointer. * - * Also see the #Argus_CreateHandle method for the corresponding - * allocation of the required memory. + * @code void Argus_Free (void * ptr) @endcode * - * @param hnd The device handle object to be deallocated. + * Also see the #Argus_CreateHandle method for the corresponding + * allocation of the required memory. + * + * @param hnd The device handle object to be deallocated. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -void Argus_DestroyHandle(argus_hnd_t *hnd); +status_t Argus_DestroyHandle(argus_hnd_t *hnd); /*!************************************************************************** * Generic API ****************************************************************************/ /*!*************************************************************************** - * @brief Gets the version number of the current API library. + * @brief Gets the version number of the current API library. * - * @details The version is compiled of a major (a), minor (b) and bugfix (c) - * number: a.b.c. + * @details The version is compiled of a major (a), minor (b) and bugfix (c) + * number: a.b.c. * - * The values are encoded into a 32-bit value: + * The values are encoded into a 32-bit value: * - * - [ 31 .. 24 ] - Major Version Number - * - [ 23 .. 16 ] - Minor Version Number - * - [ 15 .. 0 ] - Bugfix Version Number - * . + * - [ 31 .. 24 ] - Major Version Number + * - [ 23 .. 16 ] - Minor Version Number + * - [ 15 .. 0 ] - Bugfix Version Number + * . * - * To obtain the parts from the returned uin32_t value: + * To obtain the parts from the returned uin32_t value: * - * @code - * uint32_t value = Argus_GetAPIVersion(); - * uint8_t a = (value >> 24) & 0xFFU; - * uint8_t b = (value >> 16) & 0xFFU; - * uint8_t c = value & 0xFFFFU; - * @endcode + * @code + * uint32_t value = Argus_GetAPIVersion(); + * uint8_t a = (value >> 24) & 0xFFU; + * uint8_t b = (value >> 16) & 0xFFU; + * uint8_t c = value & 0xFFFFU; + * @endcode * - * @return Returns the current version number. + * @return Returns the current version number. *****************************************************************************/ uint32_t Argus_GetAPIVersion(void); /*!*************************************************************************** - * @brief Gets the build number of the current API library. + * @brief Gets the build number of the current API library. * - * @return Returns the current build number as a C-string. + * @return Returns the current build number as a C-string. *****************************************************************************/ char const *Argus_GetBuildNumber(void); /*!*************************************************************************** - * @brief Gets the version/variant of the module. + * @brief Gets the version/variant of the module. * - * @param hnd The API handle; contains all internal states and data. - * @return Returns the current module number. + * @param hnd The API handle; contains all internal states and data. + * @return Returns the current module number. *****************************************************************************/ argus_module_version_t Argus_GetModuleVersion(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Gets the version number of the chip. + * @brief Gets the name string of the module. * - * @param hnd The API handle; contains all internal states and data. - * @return Returns the current version number. + * @param hnd The API handle; contains all internal states and data. + * @return Returns the current module name. + *****************************************************************************/ +char const *Argus_GetModuleName(argus_hnd_t *hnd); + +/*!*************************************************************************** + * @brief Gets the version number of the chip. + * + * @param hnd The API handle; contains all internal states and data. + * @return Returns the current version number. *****************************************************************************/ argus_chip_version_t Argus_GetChipVersion(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Gets the type number of the device laser. + * @brief Gets the type number of the device laser. * - * @param hnd The API handle; contains all internal states and data. - * @return Returns the current device laser type number. + * @param hnd The API handle; contains all internal states and data. + * @return Returns the current device laser type number. *****************************************************************************/ argus_laser_type_t Argus_GetLaserType(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Gets the unique identification number of the chip. + * @brief Gets the unique identification number of the chip. * - * @param hnd The API handle; contains all internal states and data. - * @return Returns the unique identification number. + * @param hnd The API handle; contains all internal states and data. + * @return Returns the unique identification number. *****************************************************************************/ uint32_t Argus_GetChipID(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Gets the SPI hardware slave identifier. + * @brief Gets the SPI hardware slave identifier. * - * @param hnd The API handle; contains all internal states and data. - * @return The SPI hardware slave identifier. + * @param hnd The API handle; contains all internal states and data. + * @return The SPI hardware slave identifier. *****************************************************************************/ s2pi_slave_t Argus_GetSPISlave(argus_hnd_t *hnd); @@ -276,323 +364,425 @@ s2pi_slave_t Argus_GetSPISlave(argus_hnd_t *hnd); /*!************************************************************************** * Measurement/Device Operation **************************************************************************** - * @addtogroup argusmeas + * @addtogroup argus_meas * @{ ****************************************************************************/ /*!*************************************************************************** - * @brief Starts the timer based measurement cycle asynchronously. + * @brief Starts the timer based measurement cycle asynchronously. * * @details This function starts a timer based measurement cycle asynchronously - * in the background. A periodic timer interrupt triggers the measurement - * frames on the ASIC and the data readout afterwards. + * in the background. A periodic timer interrupt triggers the measurement + * frames on the ASIC and the data readout afterwards. * - * When the measurement has finished, a callback (which is passed as - * a parameter to the function) is invoked in order to inform the - * main thread to call the \link #Argus_EvaluateData data evaluation - * method\endlink. This call is mandatory to release the data buffer - * for the next measurement and it must not be invoked directly from - * the callback since it is currently within an interrupt service - * routine. Rather a flag should inform the main thread or task - * scheduler to invoke the evaluation as soon as possible in order - * to not introduce any unwanted delays to the next measurement frame. + * When the measurement has finished, a callback (which is passed as + * a parameter to the function) is invoked in order to inform the + * main thread to call the \link #Argus_EvaluateData data evaluation + * method\endlink. This call is mandatory to release the data buffer + * for the next measurement and it must not be invoked directly from + * the callback since it is currently within an interrupt service + * routine. Rather a flag should inform the main thread or task + * scheduler to invoke the evaluation as soon as possible in order + * to not introduce any unwanted delays to the next measurement frame. * - * The next measurement frame will be started as soon as the pre- - * conditions are meet. These are: - * 1. timer flag set (i.e. a certain time has passed since the last - * measurement in order to fulfill eye-safety), - * 2. device idle (i.e. no measurement currently ongoing) and - * 3. data buffer ready (i.e. the previous data has been evaluated). + * The next measurement frame will be started as soon as the pre- + * conditions are meet. These are: + * 1. timer flag set (i.e. a certain time has passed since the last + * measurement in order to fulfill eye-safety), + * 2. device idle (i.e. no measurement currently ongoing) and + * 3. data buffer ready (i.e. the previous data has been evaluated). * - * Usually, the device idle and data buffer ready conditions are met - * before the timer tick occurs and thus the timer dictates the frame - * rate. + * Usually, the device idle and data buffer ready conditions are met + * before the timer tick occurs and thus the timer dictates the frame + * rate. * - * The callback function pointer will be invoked when the measurement - * frame has finished successfully or whenever an error, that cannot - * be handled internally, occurs. + * The callback function pointer will be invoked when the measurement + * frame has finished successfully or whenever an error, that cannot + * be handled internally, occurs. * - * The periodic timer interrupts are used to check the measurement status - * for timeouts. An error is invoked when a measurement cycle have not - * finished within the specified time. + * The periodic timer interrupts are used to check the measurement status + * for timeouts. An error is invoked when a measurement cycle have not + * finished within the specified time. * - * Use #Argus_StopMeasurementTimer to stop the measurements. + * Use #Argus_StopMeasurementTimer to stop the measurements. * - * @note In order to use this function, the periodic interrupt timer module - * (see @ref argus_timer) must be implemented! + * @note In order to use this function, the periodic interrupt timer module + * (see @ref argus_timer) must be implemented! * - * @param hnd The API handle; contains all internal states and data. - * @param cb Callback function that will be invoked when the measurement - * is completed. Its parameters are the \link #status_t status - * \endlink and a pointer to the \link #argus_results_t results - * \endlink structure. If an error occurred, the status differs - * from #STATUS_OK and the second parameter is null. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * + * @param cb Callback function that will be invoked when the measurement + * is completed. Its parameters are the \link #status_t status + * \endlink of the finished measurement cycle and the pointer to + * the calling \link #argus_hnd_t API handle\endlink, i.e. the + * /p hnd value. The latter must be passed to the + * #Argus_EvaluateData function. + * If an error occurred, the status differs from #STATUS_OK. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_StartMeasurementTimer(argus_hnd_t *hnd, argus_callback_t cb); +status_t Argus_StartMeasurementTimer(argus_hnd_t *hnd, + argus_measurement_ready_callback_t cb); /*!*************************************************************************** - * @brief Stops the timer based measurement cycle. + * @brief Stops the timer based measurement cycle. * * @details This function stops the ongoing timer based measurement cycles - * that have been started using the #Argus_StartMeasurementTimer - * function. + * that have been started using the #Argus_StartMeasurementTimer + * function. * - * @param hnd The API handle; contains all internal states and data. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_StopMeasurementTimer(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Triggers a single measurement frame asynchronously. + * @brief Triggers a single measurement frame asynchronously. * * @details This function immediately triggers a single measurement frame - * asynchronously if all the pre-conditions are met. Otherwise it - * returns with a corresponding status (e.g. #STATUS_BUSY or - * #STATUS_ARGUS_POWERLIMIT). + * asynchronously if all the pre-conditions are met. Otherwise it + * returns with a corresponding status (e.g. #STATUS_BUSY or + * #STATUS_ARGUS_POWERLIMIT). * - * When the measurement has finished, a callback (which is passed as - * a parameter to the function) is invoked in order to inform the - * main thread to call the \link #Argus_EvaluateData data evaluation - * method\endlink. This call is mandatory to release the data buffer - * for the next measurement and it must not be invoked directly from - * the callback since it is currently within an interrupt service - * routine. Rather a flag should inform the main thread or task - * scheduler to invoke the evaluation task. + * When the measurement has finished, a callback (which is passed as + * a parameter to the function) is invoked in order to inform the + * main thread to call the \link #Argus_EvaluateData data evaluation + * method\endlink. This call is mandatory to release the data buffer + * for the next measurement and it must not be invoked directly from + * the callback since it is currently within an interrupt service + * routine. Rather a flag should inform the main thread or task + * scheduler to invoke the evaluation task. * - * The pre-conditions for starting a measurement frame are: - * 1. timer flag set (i.e. a certain time has passed since the last - * measurement in order to fulfill eye-safety), - * 2. device idle (i.e. no measurement currently ongoing) and - * 3. data buffer ready (i.e. the previous data has been evaluated). + * The pre-conditions for starting a measurement frame are: + * 1. timer flag set (i.e. a certain time has passed since the last + * measurement in order to fulfill eye-safety), + * 2. device idle (i.e. no measurement currently ongoing) and + * 3. data buffer ready (i.e. the previous data has been evaluated). * - * The callback function pointer will be invoked when the measurement - * frame has finished successfully or whenever an error, that cannot - * be handled internally, occurs. + * The callback function pointer will be invoked when the measurement + * frame has finished successfully or whenever an error, that cannot + * be handled internally, occurs. * - * The successful finishing of the measurement frame is not checked - * for timeouts! Instead, the user can call the #Argus_GetStatus() - * function on a regular function to do so. + * The successful finishing of the measurement frame is not checked + * for timeouts! Instead, the user can call the #Argus_GetStatus() + * function on a regular function to do so. * - * @param hnd The API handle; contains all internal states and data. - * @param cb Callback function that will be invoked when the measurement - * is completed. Its parameters are the \link #status_t status - * \endlink and a pointer to the \link #argus_results_t results - * \endlink structure. If an error occurred, the status differs - * from #STATUS_OK and the second parameter is null. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @note Despite this function triggers a new measurement cycle upon its + * invocation, the frame time parameter is still active for this + * measurement mode. Basically, the first pre-condition mentioned + * above is controlled via the frame time parameter. This means + * that measurements cannot be triggered faster than the frame + * timer parameters specifies. The maximum integration time (i.e. + * exposure time) is also determined by the frame time such that + * new measurements are finished with the specified frame time and + * the device is ready to trigger a new measurement after the + * frame time has elapse. + * See #Argus_SetConfigurationFrameTime function for more information + * on the frame time. + * + * @param hnd The API handle; contains all internal states and data. + * + * @param cb Callback function that will be invoked when the measurement + * is completed. Its parameters are the \link #status_t status + * \endlink of the finished measurement cycle and the pointer to + * the calling \link #argus_hnd_t API handle\endlink, i.e. the + * /p hnd value. The latter must be passed to the + * #Argus_EvaluateData function. + * If an error occurred, the status differs from #STATUS_OK. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_TriggerMeasurement(argus_hnd_t *hnd, argus_callback_t cb); +status_t Argus_TriggerMeasurement(argus_hnd_t *hnd, + argus_measurement_ready_callback_t cb); /*!*************************************************************************** - * @brief Stops the currently ongoing measurements and SPI activity immediately. + * @brief Determines whether a data evaluation is pending. * - * @param hnd The API handle; contains all internal states and data. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @details If the function returns true, a raw buffer is required to be + * evaluated to the #Argus_EvaluateData function. The raw data buffer + * is filled with raw data from the measurement tasks which need to + * be evaluated and the buffer must be freed in order to restart a + * new measurement task. + * + * Note that no configuration parameters can be update until all raw + * buffers are evaluated. + * + * @note See also the #Argus_GetStatus function to obtain the current device + * status and error code if any. + * + * @param hnd The API handle; contains all internal states and data. + * @return True if any raw buffer is filled with data that must be evaluated. + *****************************************************************************/ +bool Argus_IsDataEvaluationPending(argus_hnd_t *hnd); + +/*!*************************************************************************** + * @brief Determines if the device if active with timer based measurements. + * @details If the function returns true, the device is active with timer + * scheduled measurements that have been started via the + * #Argus_StartMeasurementTimer. + * + * Note that the active state is independent of the busy state that + * is set when the device is actually busy. The active state is also + * true if the device is currently idle but waits for the next timer + * event to trigger a new measurement cycle. + * + * @note See also the #Argus_GetStatus function to obtain the current device + * status and error code if any. + * + * @param hnd The API handle; contains all internal states and data. + * @return True if the device is operating in timer triggered measurement mode. + *****************************************************************************/ +bool Argus_IsTimerMeasurementActive(argus_hnd_t *hnd); + +/*!*************************************************************************** + * @brief Stops the currently ongoing measurements and SPI activity immediately. + * + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_Abort(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Checks the state of the device/driver. + * @brief Checks the state of the device/driver. * - * @details Returns the current module status or error if any. - * See the following for a list of errors: + * @details Returns the current module status or error if any. * - * Status: - * - Idle/OK: Device and SPI interface are idle (== #STATUS_IDLE). - * - Busy: Device or SPI interface are busy (== #STATUS_BUSY). - * - Initializing: The modules and devices are currently initializing - * (== #STATUS_INITIALIZING). - * . + * See the following for a list of errors: * - * Error: - * - Not Initialized: The modules (or any submodule) has not been - * initialized yet (== #ERROR_NOT_INITIALIZED). - * - Not Connected: No device has been connected (or connection errors - * have occurred) (== #ERROR_ARGUS_NOT_CONNECTED). - * - Timeout: A previous frame measurement has not finished within a - * specified time (== #ERROR_TIMEOUT). - * . + * Status: + * - Idle/OK: Device and SPI interface are idle (== #STATUS_IDLE). + * - Busy: Device or SPI interface are busy (== #STATUS_BUSY). + * - Initializing: The modules and devices are currently initializing + * (== #STATUS_INITIALIZING). + * . * - * @param hnd The API handle; contains all internal states and data. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * Error: + * - Not Initialized: The modules (or any submodule) has not been + * initialized yet (== #ERROR_NOT_INITIALIZED). + * - Not Connected: No device has been connected (or connection errors + * have occurred) (== #ERROR_ARGUS_NOT_CONNECTED). + * - Timeout: A previous frame measurement has not finished within a + * specified time (== #ERROR_TIMEOUT). + * . + * + * @note Note that this function returns the actual busy state. This means + * that it will return #STATUS_IDLE during the pause between two + * consecutive measurement frames. If the device is active with timer + * based measurements (i.e. started via the #Argus_StartMeasurementTimer + * function), the return state switches from idle to busy and back + * periodically. Use the #Argus_IsTimerMeasurementActive function in + * order to determine if the device is active with timer based + * measurements. + * + * @note Note also that the device might reject configuration parameter + * update despite the status is #STATUS_IDLE. This is due to the fact + * that the internal raw data buffers are still busy and require to + * be freed by passing them to the #Argus_EvaluateData function. Use + * the #Argus_IsDataEvaluationPending function to see whether any of + * the raw data buffers is busy or the configuration can be changed. + * + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetStatus(argus_hnd_t *hnd); /*!***************************************************************************** - * @brief Tests the connection to the device by sending a ping message. + * @brief Tests the connection to the device by sending a ping message. * - * @details A ping is transfered to the device in order to check the device and - * SPI connection status. Returns #STATUS_OK on success and - * #ERROR_ARGUS_NOT_CONNECTED elsewise. + * @details A ping is transferred to the device in order to check the device and + * SPI connection status. Returns #STATUS_OK on success and + * #ERROR_ARGUS_NOT_CONNECTED else-wise. * - * @param hnd The API handle; contains all internal states and data. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). ******************************************************************************/ status_t Argus_Ping(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Evaluate useful information from the raw measurement data. + * @brief Evaluates measurement data from the raw sensor readout data. * - * @details This function is called with a pointer to the raw results obtained - * from the measurement cycle. It evaluates this data and creates - * useful information from it. Furthermore, calibration is applied to - * the data. Finally, the results are used in order to adapt the device - * configuration to the ambient conditions in order to achieve optimal - * device performance. + * @details This function must be called after each completion of a measurement + * cycle. The completion of a measurement cycle is communicated by the + * API via the invocation of the measurement data ready callback. The + * callback is installed in the API when new measurements are started + * either via the #Argus_TriggerMeasurement or via the + * #Argus_StartMeasurementTimer functions. * - * Therefore, it consists of the following sub-functions: - * - Apply pre-calibration: Applies calibration steps before evaluating - * the data, i.e. calculations that are to the integration results - * directly. - * - Evaluate data: Calculates measurement parameters such as range, - * amplitude or ambient light intensity, depending on the configurations. - * - Apply post-calibration: Applies calibrations after evaluation of - * measurement data, i.e. calibrations applied to the calculated - * values such as range. - * - Dynamic Configuration Adaption: checks if the configuration needs - * to be adjusted before the next measurement cycle in order to - * achieve optimum performance. Note that the configuration might not - * applied directly but before the next measurement starts. This is - * due to the fact that the device could be busy measuring already - * the next frame and thus no SPI activity is allowed. - * . - * However, if the device is idle, the configuration will be written - * immediately. + * This function evaluates measurement values like distances, amplitudes + * states and auxiliary values like temperature or voltage values from + * the raw sensor readout data obtained from the device during the + * measurement cycle. A pointer to a #argus_results_t data structure + * must be passed where all the evaluated values will be written to. + * The structure must persist during the whole execution of the + * #Argus_EvaluateData function. * - * @param hnd The API handle; contains all internal states and data. - * @param res A pointer to the results structure that will be populated - * with evaluated data. - * @param raw The pointer to the raw data that has been obtained by the - * measurement finished callback. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * In addition to the evaluation of measurement data, the function + * feeds back the obtained information to the device in order to + * optimize its performance with respect to the ambient conditions, + * utilizing the so called Dynamic Configuration Adaption (DCA) + * feature. + * + * Furthermore, several calibration algorithm are applied to the data. + * + * If the function is called without any data ready to be evaluated + * from the measurement module, the error code #ERROR_ARGUS_BUFFER_EMPTY + * is returned and not data is written to the passed #argus_results_t + * data structure. + * + * @note The call to this function is mandatory for each finished measurement + * cycle, i.e. for each call to the measurement data ready callback. + * If the function is not called, the data is not evaluated and the + * internal raw data buffers are not freed. In that case, they can not + * be reused for the next measurement and the API can not start new + * measurements. + * There are up to two internal buffers available, the to callback + * is called twice before the API must wait for the data evaluation + * to finish. + * + * @param hnd The API handle; contains all internal states and data. + * @param res A pointer to the results structure that will be populated + * with evaluated data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_EvaluateData(argus_hnd_t *hnd, argus_results_t *res, void *raw); +status_t Argus_EvaluateData(argus_hnd_t *hnd, argus_results_t *res); /*!*************************************************************************** - * @brief Executes a crosstalk calibration measurement. + * @brief Evaluates measurement data from the raw sensor readout data. + * + * @details This function enhances the #Argus_EvaluateData by adding additional + * debug data into a specified debug data structure (\p dbg). If the + * \p dbg is null, the function is eqivalent to the #Argus_EvaluateData + * function. This, see #Argus_EvaluateData for reference. + * + * @param hnd The API handle; contains all internal states and data. + * @param res A pointer to the results structure that will be populated + * with evaluated data. + * @param dbg An optional pointer (can be null) to the debug data structure. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_EvaluateDataDebug(argus_hnd_t *hnd, argus_results_t *res, + argus_results_debug_t *dbg); + +/*!*************************************************************************** + * @brief Executes a crosstalk calibration measurement. * * @details This function immediately triggers a crosstalk vector calibration - * measurement sequence. The ordinary measurement activity is suspended - * while the calibration is ongoing. + * measurement sequence. The ordinary measurement activity is suspended + * while the calibration is ongoing. * - * In order to perform a crosstalk calibration, the reflection of the - * transmitted signal must be kept from the receiver side, by either - * covering the TX completely (or RX respectively) or by setting up - * an absorbing target at far distance. + * In order to perform a crosstalk calibration, the reflection of the + * transmitted signal must be kept from the receiver side, by either + * covering the TX completely (or RX respectively) or by setting up + * an absorbing target at far distance. * - * After calibration has finished successfully, the obtained data is - * applied immediately and can be read from the API using the - * #Argus_GetCalibrationCrosstalkVectorTable function. + * After calibration has finished successfully, the obtained data is + * applied immediately and can be read from the API using the + * #Argus_GetCalibrationCrosstalkVectorTable function. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_ExecuteXtalkCalibrationSequence(argus_hnd_t *hnd, argus_mode_t mode); - +status_t Argus_ExecuteXtalkCalibrationSequence(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Executes a relative range offset calibration measurement. + * @brief Executes a relative range offset calibration measurement. * * @details This function immediately triggers a relative range offset calibration - * measurement sequence. The ordinary measurement activity is suspended - * while the calibration is ongoing. + * measurement sequence. The ordinary measurement activity is suspended + * while the calibration is ongoing. * - * In order to perform a relative range offset calibration, a flat - * calibration target must be setup perpendicular to the sensors - * field-of-view. + * In order to perform a relative range offset calibration, a flat + * calibration target must be setup perpendicular to the sensors + * field-of-view. * - * \code + * \code * AFBR-S50 ToF Sensor - * #| - * #| | - * #|-----+ | - * #| Rx | | - * Reference #|----++ | Calibration - * Plane #| Tx | | Target - * #|----+ | - * #| | - * #| <------- targetRange -----------------> | - * \endcode + * #| + * #| | + * #|-----+ | + * #| RX | | + * Reference #|----++ | Calibration + * Plane #| TX | | Target + * #|----+ | + * #| | + * #| <------- targetRange -----------------> | + * \endcode * - * There are two options to run the offset calibration: relative and - * absolute. - * - Relative (#Argus_ExecuteRelativeRangeOffsetCalibrationSequence): - * when the absolute distance is not essential or the distance to - * the calibration target is not known, the relative method can be - * used to compensate the relative pixel range offset w.r.t. the - * average range. The absolute or global range offset is not changed. - * - Absolute (#Argus_ExecuteAbsoluteRangeOffsetCalibrationSequence): - * when the absolute distance is essential and the distance to the - * calibration target is known, the absolute method can be used to - * calibrate the absolute measured distance. Additionally, the - * relative pixel offset w.r.t. the average range is also compensated. - * . + * There are two options to run the offset calibration: relative and + * absolute. * - * After calibration has finished successfully, the obtained data is - * applied immediately and can be read from the API using the - * #Argus_GetCalibrationPixelRangeOffsets or - * #Argus_GetCalibrationGlobalRangeOffset function. + * - Relative (#Argus_ExecuteRelativeRangeOffsetCalibrationSequence): + * when the absolute distance is not essential or the distance to + * the calibration target is not known, the relative method can be + * used to compensate the relative pixel range offset w.r.t. the + * average range. The absolute or global range offset is not changed. + * - Absolute (#Argus_ExecuteAbsoluteRangeOffsetCalibrationSequence): + * when the absolute distance is essential and the distance to the + * calibration target is known, the absolute method can be used to + * calibrate the absolute measured distance. Additionally, the + * relative pixel offset w.r.t. the average range is also compensated. + * . * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * After calibration has finished successfully, the obtained data is + * applied immediately and can be read from the API using the + * #Argus_GetCalibrationPixelRangeOffsets or + * #Argus_GetCalibrationGlobalRangeOffset function. + * + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_ExecuteRelativeRangeOffsetCalibrationSequence(argus_hnd_t *hnd, - argus_mode_t mode); +status_t Argus_ExecuteRelativeRangeOffsetCalibrationSequence(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Executes an absolute range offset calibration measurement. + * @brief Executes an absolute range offset calibration measurement. * * @details This function immediately triggers an absolute range offset calibration - * measurement sequence. The ordinary measurement activity is suspended - * while the calibration is ongoing. + * measurement sequence. The ordinary measurement activity is suspended + * while the calibration is ongoing. * - * In order to perform a relative range offset calibration, a flat - * calibration target must be setup perpendicular to the sensors - * field-of-view. + * In order to perform a relative range offset calibration, a flat + * calibration target must be setup perpendicular to the sensors + * field-of-view. * - * \code + * \code * AFBR-S50 ToF Sensor - * #| - * #| | - * #|-----+ | - * #| Rx | | - * Reference #|----++ | Calibration - * Plane #| Tx | | Target - * #|----+ | - * #| | - * #| <------- targetRange -----------------> | - * \endcode + * #| + * #| | + * #|-----+ | + * #| RX | | + * Reference #|----++ | Calibration + * Plane #| TX | | Target + * #|----+ | + * #| | + * #| <------- targetRange -----------------> | + * \endcode * - * There are two options to run the offset calibration: relative and - * absolute. - * - Relative (#Argus_ExecuteRelativeRangeOffsetCalibrationSequence): - * when the absolute distance is not essential or the distance to - * the calibration target is not known, the relative method can be - * used to compensate the relative pixel range offset w.r.t. the - * average range. The absolute or global range offset is not changed. - * - Absolute (#Argus_ExecuteAbsoluteRangeOffsetCalibrationSequence): - * when the absolute distance is essential and the distance to the - * calibration target is known, the absolute method can be used to - * calibrate the absolute measured distance. Additionally, the - * relative pixel offset w.r.t. the average range is also compensated. - * . + * There are two options to run the offset calibration: relative and + * absolute. * - * After calibration has finished successfully, the obtained data is - * applied immediately and can be read from the API using the - * #Argus_GetCalibrationPixelRangeOffsets or - * #Argus_GetCalibrationGlobalRangeOffset function. + * - Relative (#Argus_ExecuteRelativeRangeOffsetCalibrationSequence): + * when the absolute distance is not essential or the distance to + * the calibration target is not known, the relative method can be + * used to compensate the relative pixel range offset w.r.t. the + * average range. The absolute or global range offset is not changed. + * - Absolute (#Argus_ExecuteAbsoluteRangeOffsetCalibrationSequence): + * when the absolute distance is essential and the distance to the + * calibration target is known, the absolute method can be used to + * calibrate the absolute measured distance. Additionally, the + * relative pixel offset w.r.t. the average range is also compensated. + * . * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param targetRange The absolute range between the reference plane and the - * calibration target in meter an Q9.22 format. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * After calibration has finished successfully, the obtained data is + * applied immediately and can be read from the API using the + * #Argus_GetCalibrationPixelRangeOffsets or + * #Argus_GetCalibrationGlobalRangeOffset function. + * + * @param hnd The API handle; contains all internal states and data. + * @param targetRange The absolute range between the reference plane and the + * calibration target in meter an Q9.22 format. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_ExecuteAbsoluteRangeOffsetCalibrationSequence(argus_hnd_t *hnd, - argus_mode_t mode, q9_22_t targetRange); /*! @} */ @@ -600,188 +790,245 @@ status_t Argus_ExecuteAbsoluteRangeOffsetCalibrationSequence(argus_hnd_t *hnd, /*!************************************************************************** * Configuration API **************************************************************************** - * @addtogroup arguscfg + * @addtogroup argus_cfg * @{ ****************************************************************************/ /*!*************************************************************************** - * @brief Sets the measurement mode to a specified device. + * @brief Gets the default measurement mode for a specified module type. * - * @param hnd The API handle; contains all internal states and data. - * @param value The new measurement mode. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param module The specified module type. + * @return Returns the default measurement mode for the specified module type. *****************************************************************************/ -status_t Argus_SetConfigurationMeasurementMode(argus_hnd_t *hnd, - argus_mode_t value); +argus_mode_t Argus_GetDefaultMeasurementMode(argus_module_version_t module); /*!*************************************************************************** - * @brief Gets the measurement mode from a specified device. + * @brief Sets the measurement mode to a specified device. * - * @param hnd The API handle; contains all internal states and data. - * @param value The current measurement mode. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @details This generates a new default configuration and calibration for the + * specified measurement mode and applies it to the device. + * + * See #argus_mode_t for a list of all available measurement modes. + * + * @warning The function overwrites all made changes to the configuration or + * calibration parameters with the default values. So this function + * must be called before any other changes to the configuration or + * calibration parameters are made! + * + * @param hnd The API handle; contains all internal states and data. + * @param mode The new measurement mode. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_GetConfigurationMeasurementMode(argus_hnd_t *hnd, - argus_mode_t *value); +status_t Argus_SetMeasurementMode(argus_hnd_t *hnd, argus_mode_t mode); /*!*************************************************************************** - * @brief Sets the frame time to a specified device. + * @brief Resets the measurement mode to a specified device. * - * @param hnd The API handle; contains all internal states and data. - * @param value The measurement frame time in microseconds. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @details This generates a new default configuration and calibration for the + * current measurement mode and applies it to the device. + * + * @warning The function overwrites all made changes to the configuration or + * calibration parameters with the default values. So this function + * must be called before any other changes to the configuration or + * calibration parameters are made! + * + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_ResetMeasurementMode(argus_hnd_t *hnd); + +/*!*************************************************************************** + * @brief Gets the measurement mode from a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param mode The current measurement mode. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_GetMeasurementMode(argus_hnd_t *hnd, argus_mode_t *mode); + +/*!*************************************************************************** + * @brief Sets the frame time to a specified device. + * + * @details The frame time determines the measurement rate of the device. + * Usually, this controller the periodicity of measurements to be + * triggered via the timer based measurement mode that can be started + * via the #Argus_StartMeasurementTimer function. But also the + * behavior of the #Argus_TriggerMeasurement function is influenced + * by the frame rate parameter. + * + * The frame time parameter handles the maximum frame rate by limiting + * the trigger of a new measurement frame to the specified value. + * On the other hand, the accuracy of measurement results it also + * influenced since the frame time specifies the maximum integration + * depth (i.e. exposure time) along with the laser safety limitations. + * This means, the measurement speed can be increased by decreasing + * the frame time parameter and the accuracy can be improved by + * increasing the frame time parameter. + * + * Note the additional factor will limit the maximum frame rate on the + * one hand and the accuracy on the other hand: + * - High CPU load (or slow CPU in general) will lead to delays due + * to long data evaluation task (#Argus_EvaluateData) or long user + * application code. Reduce CPU load or increase CPU power to + * increase maximum frame rate. + * - The dual frequency mode (DFM, see #Argus_SetConfigurationDFMMode) + * will additionally limit the maximum frame rate to approximately + * 100 frames per second. Disable the DFM to increase maximum frame + * rates. + * - The smart power save (SPS, see + * #Argus_SetConfigurationSmartPowerSaveEnabled) mode will decrease + * the maximum possible frame rate slightly. Disable it to increase + * the maximum frame rate. + * - The dynamic configuration adaption with its specific power saving + * ratio parameter (see #Argus_SetConfigurationDynamicAdaption) + * will limit the maximum integration depth along with the laser + * safety limitations. Increase the power saving ratio to increase + * accuracy. Note that laser safety limitations might already limit + * the maximum integration depth such that the power saving ratio + * is ineffective. + * . + * + * @param hnd The API handle; contains all internal states and data. + * @param value The measurement frame time in microseconds. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetConfigurationFrameTime(argus_hnd_t *hnd, uint32_t value); /*!*************************************************************************** - * @brief Gets the frame time from a specified device. + * @brief Gets the frame time from a specified device. * - * @param hnd The API handle; contains all internal states and data. - * @param value The current frame time in microseconds. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current frame time in microseconds. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetConfigurationFrameTime(argus_hnd_t *hnd, uint32_t *value); /*!*************************************************************************** - * @brief Sets the smart power save enabled flag to a specified device. - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new smart power save enabled flag. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Sets the smart power save enabled flag to a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The new smart power save enabled flag. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetConfigurationSmartPowerSaveEnabled(argus_hnd_t *hnd, - argus_mode_t mode, bool value); /*!*************************************************************************** - * @brief Gets the smart power save enabled flag from a specified device. - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current smart power save enabled flag. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Gets the smart power save enabled flag from a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The current smart power save enabled flag. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetConfigurationSmartPowerSaveEnabled(argus_hnd_t *hnd, - argus_mode_t mode, bool *value); /*!*************************************************************************** - * @brief Sets the Dual Frequency Mode (DFM) to a specified device. - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new DFM mode value. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Sets the Dual Frequency Mode (DFM) to a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The new DFM mode value. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetConfigurationDFMMode(argus_hnd_t *hnd, - argus_mode_t mode, argus_dfm_mode_t value); /*!*************************************************************************** - * @brief Gets the Dual Frequency Mode (DFM) from a specified device. - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current DFM mode value. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Gets the Dual Frequency Mode (DFM) from a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The current DFM mode value. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetConfigurationDFMMode(argus_hnd_t *hnd, - argus_mode_t mode, argus_dfm_mode_t *value); /*!*************************************************************************** - * @brief Sets the Shot Noise Monitor (SNM) mode to a specified device. - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new SNM mode value. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Sets the Shot Noise Monitor (SNM) mode to a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The new SNM mode value. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetConfigurationShotNoiseMonitorMode(argus_hnd_t *hnd, - argus_mode_t mode, argus_snm_mode_t value); /*!*************************************************************************** - * @brief Gets the Shot Noise Montor (SNM) mode from a specified device. - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current SNM mode value. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Gets the Shot Noise Monitor (SNM) mode from a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The current SNM mode value. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetConfigurationShotNoiseMonitorMode(argus_hnd_t *hnd, - argus_mode_t mode, argus_snm_mode_t *value); -#if 0 -///*!*************************************************************************** -// * @brief Sets the Crosstalk Monitor (XTM) mode to a specified device. -// * @param hnd The API handle; contains all internal states and data. -// * @param mode The targeted measurement mode. -// * @param value The new XTM mode value (true: enabled; false: disabled). -// * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). -// *****************************************************************************/ -//status_t Argus_SetConfigurationCrosstalkMonitorMode(argus_hnd_t * hnd, -// argus_mode_t mode, -// bool value); -// -///*!*************************************************************************** -// * @brief Gets the Crosstalk Monitor (XTM) mode from a specified device. -// * @param hnd The API handle; contains all internal states and data. -// * @param mode The targeted measurement mode. -// * @param value The current XTM mode value (true: enabled; false: disabled). -// * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). -// *****************************************************************************/ -//status_t Argus_GetConfigurationCrosstalkMonitorMode(argus_hnd_t * hnd, -// argus_mode_t mode, -// bool * value); -#endif /*!*************************************************************************** - * @brief Sets the full DCA module configuration to a specified device. +* @brief Sets the Crosstalk Monitor (XTM) mode to a specified device. +* +* @param hnd The API handle; contains all internal states and data. +* @param value The new XTM mode value (true: enabled; false: disabled). +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +*****************************************************************************/ +status_t Argus_SetConfigurationCrosstalkMonitorMode(argus_hnd_t *hnd, + bool value); + +/*!*************************************************************************** +* @brief Gets the Crosstalk Monitor (XTM) mode from a specified device. +* +* @param hnd The API handle; contains all internal states and data. +* @param value The current XTM mode value (true: enabled; false: disabled). +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +*****************************************************************************/ +status_t Argus_GetConfigurationCrosstalkMonitorMode(argus_hnd_t *hnd, + bool *value); + +/*!*************************************************************************** + * @brief Sets the full DCA module configuration to a specified device. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new DCA configuration set. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The new DCA configuration set. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetConfigurationDynamicAdaption(argus_hnd_t *hnd, - argus_mode_t mode, argus_cfg_dca_t const *value); /*!*************************************************************************** - * @brief Gets the # from a specified device. + * @brief Gets the # from a specified device. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current DCA configuration set value. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current DCA configuration set value. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetConfigurationDynamicAdaption(argus_hnd_t *hnd, - argus_mode_t mode, argus_cfg_dca_t *value); /*!*************************************************************************** - * @brief Sets the pixel binning configuration parameters to a specified device. - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new pixel binning configuration parameters. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Sets the pixel binning configuration parameters to a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The new pixel binning configuration parameters. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetConfigurationPixelBinning(argus_hnd_t *hnd, - argus_mode_t mode, argus_cfg_pba_t const *value); /*!*************************************************************************** - * @brief Gets the pixel binning configuration parameters from a specified device. - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current pixel binning configuration parameters. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Gets the pixel binning configuration parameters from a specified device. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The current pixel binning configuration parameters. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetConfigurationPixelBinning(argus_hnd_t *hnd, - argus_mode_t mode, argus_cfg_pba_t *value); /*!*************************************************************************** - * @brief Gets the current unambiguous range in mm. - * @param hnd The API handle; contains all internal states and data. - * @param range_mm The returned range in mm. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Gets the current unambiguous range in mm. + * + * @param hnd The API handle; contains all internal states and data. + * @param range_mm The returned range in mm. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetConfigurationUnambiguousRange(argus_hnd_t *hnd, uint32_t *range_mm); @@ -791,458 +1038,398 @@ status_t Argus_GetConfigurationUnambiguousRange(argus_hnd_t *hnd, /*!************************************************************************** * Calibration API **************************************************************************** - * @addtogroup arguscal + * @addtogroup argus_cal * @{ ****************************************************************************/ /*!*************************************************************************** - * @brief Sets the global range offset value to a specified device. + * @brief Sets the global range offset value to a specified device. * - * @details The global range offset is subtracted from the raw range values. + * @details The global range offset is subtracted from the raw range values. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new global range offset in meter and Q0.15 format. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The new global range offset in meter and Q0.15 format. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetCalibrationGlobalRangeOffset(argus_hnd_t *hnd, - argus_mode_t mode, q0_15_t value); /*!*************************************************************************** - * @brief Gets the global range offset value from a specified device. + * @brief Gets the global range offset value from a specified device. * - * @details The global range offset is subtracted from the raw range values. + * @details The global range offset is subtracted from the raw range values. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current global range offset in meter and Q0.15 format. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current global range offset in meter and Q0.15 format. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetCalibrationGlobalRangeOffset(argus_hnd_t *hnd, - argus_mode_t mode, q0_15_t *value); /*!*************************************************************************** - * @brief Sets the relative pixel offset table to a specified device. + * @brief Sets the relative pixel offset table to a specified device. * * @details The relative pixel offset values are subtracted from the raw range - * values for each individual pixel. Note that a global range offset - * is applied additionally. The relative pixel offset values are meant - * to be with respect to the average range of all pixels, i.e. the - * average of all relative offsets should be 0! + * values for each individual pixel. Note that a global range offset + * is applied additionally. The relative pixel offset values are meant + * to be with respect to the average range of all pixels, i.e. the + * average of all relative offsets should be 0! * - * The crosstalk vector table is a two dimensional array of type - * #q0_15_t. + * The crosstalk vector table is a two dimensional array of type + * #q0_15_t, wrapped within the #argus_cal_offset_table_t structure. * - * The dimensions are: - * - size(0) = #ARGUS_PIXELS_X (Pixel count in x-direction) - * - size(1) = #ARGUS_PIXELS_Y (Pixel count in y-direction) - * . + * The dimensions are: + * - size(0) = #ARGUS_PIXELS_X (Pixel count in x-direction) + * - size(1) = #ARGUS_PIXELS_Y (Pixel count in y-direction) + * . * - * Its recommended to use the built-in pixel offset calibration - * sequence (see #Argus_ExecuteRelativeRangeOffsetCalibrationSequence) - * to determine the offset table for the current device. + * Its recommended to use the built-in pixel offset calibration + * sequence (see #Argus_ExecuteRelativeRangeOffsetCalibrationSequence) + * to determine the offset table for the current device. * - * If a constant offset table for all device needs to be incorporated - * into the sources, the #Argus_GetExternalPixelRangeOffsets_Callback - * should be used. + * If a constant offset table for all device needs to be incorporated + * into the sources, the #Argus_GetPixelRangeOffsets_Callback + * should be used. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new relative range offset in meter and Q0.15 format. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The new relative range offset in meter and Q0.15 format. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_SetCalibrationPixelRangeOffsets(argus_hnd_t *hnd, argus_mode_t mode, - q0_15_t value[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); +status_t Argus_SetCalibrationPixelRangeOffsets(argus_hnd_t *hnd, + argus_cal_offset_table_t const *value); /*!*************************************************************************** - * @brief Gets the relative pixel offset table from a specified device. + * @brief Gets the relative pixel offset table from a specified device. * * @details The relative pixel offset values are subtracted from the raw range - * values for each individual pixel. Note that a global range offset - * is applied additionally. The relative pixel offset values are meant - * to be with respect to the average range of all pixels, i.e. the - * average of all relative offsets should be 0! + * values for each individual pixel. Note that a global range offset + * is applied additionally. The relative pixel offset values are meant + * to be with respect to the average range of all pixels, i.e. the + * average of all relative offsets should be 0! * - * The crosstalk vector table is a two dimensional array of type - * #q0_15_t. + * The crosstalk vector table is a two dimensional array of type + * #q0_15_t, wrapped within the #argus_cal_offset_table_t structure. * - * The dimensions are: - * - size(0) = #ARGUS_PIXELS_X (Pixel count in x-direction) - * - size(1) = #ARGUS_PIXELS_Y (Pixel count in y-direction) - * . + * The dimensions are: + * - size(0) = #ARGUS_PIXELS_X (Pixel count in x-direction) + * - size(1) = #ARGUS_PIXELS_Y (Pixel count in y-direction) + * . * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current relative range offset in meter and Q0.15 format. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current relative range offset in meter and Q0.15 format. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_GetCalibrationPixelRangeOffsets(argus_hnd_t *hnd, argus_mode_t mode, - q0_15_t value[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); +status_t Argus_GetCalibrationPixelRangeOffsets(argus_hnd_t *hnd, + argus_cal_offset_table_t *value); /*!*************************************************************************** - * @brief Gets the relative pixel offset table from a specified device. + * @brief Resets the relative pixel offset values for the specified device to + * the factory calibrated default values. * * @details The relative pixel offset values are subtracted from the raw range - * values for each individual pixel. Note that a global range offset - * is applied additionally. The relative pixel offset values are meant - * to be with respect to the average range of all pixels, i.e. the - * average of all relative offsets should be 0! + * values for each individual pixel. Note that a global range offset + * is applied additionally. * - * The crosstalk vector table is a two dimensional array of type - * #q0_15_t. + * The factory defaults are device specific values. * - * The dimensions are: - * - size(0) = #ARGUS_PIXELS_X (Pixel count in x-direction) - * - size(1) = #ARGUS_PIXELS_Y (Pixel count in y-direction) - * - * The total offset table consists of the custom pixel offset values - * (set via #Argus_SetCalibrationPixelRangeOffsets) and the internal, - * factory calibrated device specific offset values. - * This is informational only! - * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current total relative range offset in meter and Q0.15 format. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_GetCalibrationTotalPixelRangeOffsets(argus_hnd_t *hnd, argus_mode_t mode, - q0_15_t value[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); - +status_t Argus_ResetCalibrationPixelRangeOffsets(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Resets the relative pixel offset values for the specified device to - * the factory calibrated default values. - * - * @details The relative pixel offset values are subtracted from the raw range - * values for each individual pixel. Note that a global range offset - * is applied additionally. - * - * The factory defaults are device specific values. - * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). - *****************************************************************************/ -status_t Argus_ResetCalibrationPixelRangeOffsets(argus_hnd_t *hnd, argus_mode_t mode); - -/*!*************************************************************************** - * @brief A callback that returns the external pixel range offsets. + * @brief A callback that returns the external pixel range offsets. * * @details The function needs to be implemented by the host application in - * order to set the external pixel range offsets values upon system - * initialization. If not defined in user code, the default - * implementation will return an all zero offset table, assuming there - * is no (additional) external pixel range offset values. + * order to set the external pixel range offsets values upon system + * initialization. If not defined in user code, the default + * implementation will return an all zero offset table, assuming there + * is no (additional) external pixel range offset values. * - * If defined in user code, the function must fill all offset values - * in the provided \par offsets parameter with external range offset - * values. - * The values can be obtained by the calibration routine. + * If defined in user code, the function must fill all offset values + * in the provided \par offsets parameter with external range offset + * values. + * The values can be obtained by the calibration routine. * - * Example usage: + * Example usage: * - * @code - * status_t Argus_GetExternalPixelRangeOffsets_Callback(q0_15_t offsets[ARGUS_PIXELS_X][ARGUS_PIXELS_Y], - * argus_mode_t mode) - * { - * (void) mode; // Ignore mode; use same values for all modes. - * memset(offsets, 0, sizeof(q0_15_t) * ARGUS_PIXELS); + * @code + * status_t Argus_GetPixelRangeOffsets_Callback(argus_cal_offset_table_t offsets) + * { + * memset(offsets, 0, sizeof(argus_cal_offset_t)); * - * // Set offset values in meter and Q0.15 format. - * offsets[0][0].dS = -16384; offsets[0][0].dC = -32768; - * offsets[0][1].dS = -32768; offsets[0][1].dC = 0; - * offsets[0][2].dS = 16384; offsets[0][2].dC = -16384; - * // etc. - * } - * @endcode + * // Set offset values in meter and Q0.15 format. + * offsets.Table[0][0] = -3542; + * offsets.Table[0][1] = -4385; + * offsets.Table[0][2] = 2953; + * // etc. + * } + * @endcode * - * @param offsets The pixel range offsets in meter and Q0.15 format; to be - * filled with data. - * @param mode Determines the current measurement mode; can be ignored if - * only a single measurement mode is utilized. + * @param offsets The pixel range offsets in meter and Q0.15 format; to be + * filled with data. + * @param mode The current measurement mode. *****************************************************************************/ -void Argus_GetExternalPixelRangeOffsets_Callback(q0_15_t offsets[ARGUS_PIXELS_X][ARGUS_PIXELS_Y], - argus_mode_t mode); +void Argus_GetPixelRangeOffsets_Callback(argus_cal_offset_table_t *offsets, + argus_mode_t const mode); /*!*************************************************************************** - * @brief Sets the sample time for the range offset calibration sequence. + * @brief Sets the sample time for the range offset calibration sequence. * - * @details Gets the measurement sample acquisition time for executing the - * range offset calibration sequence and generate the offset data.\n - * Units: msec. + * @details Gets the measurement sample acquisition time for executing the + * range offset calibration sequence and generate the offset data.\n + * Units: msec. * - * @param hnd The API handle; contains all internal states and data. - * @param value The new range offset calibration sequence sample time. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The new range offset calibration sequence sample time. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetCalibrationRangeOffsetSequenceSampleTime(argus_hnd_t *hnd, uint16_t value); /*!*************************************************************************** - * @brief Gets the sample time for the range offset calibration sequence. + * @brief Gets the sample time for the range offset calibration sequence. * - * @details Gets the measurement sample acquisition time for executing the - * range offset calibration sequence and generate the ooffset data.\n - * Units: msec. + * @details Gets the measurement sample acquisition time for executing the + * range offset calibration sequence and generate the offset data.\n + * Units: msec. * - * @param hnd The API handle; contains all internal states and data. - * @param value The current range offset calibration sequence sample time. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current range offset calibration sequence sample time. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetCalibrationRangeOffsetSequenceSampleTime(argus_hnd_t *hnd, uint16_t *value); /*!*************************************************************************** - * @brief Sets the pixel-to-pixel crosstalk compensation parameters to a specified device. + * @brief Sets the pixel-to-pixel crosstalk compensation parameters to a specified device. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new pixel-to-pixel crosstalk compensation parameters. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The new pixel-to-pixel crosstalk compensation parameters. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetCalibrationCrosstalkPixel2Pixel(argus_hnd_t *hnd, - argus_mode_t mode, argus_cal_p2pxtalk_t const *value); /*!*************************************************************************** - * @brief Gets the pixel-to-pixel crosstalk compensation parameters from a specified device. + * @brief Gets the pixel-to-pixel crosstalk compensation parameters from a specified device. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current pixel-to-pixel crosstalk compensation parameters. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current pixel-to-pixel crosstalk compensation parameters. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetCalibrationCrosstalkPixel2Pixel(argus_hnd_t *hnd, - argus_mode_t mode, argus_cal_p2pxtalk_t *value); /*!*************************************************************************** - * @brief Sets the custom crosstalk vector table to a specified device. + * @brief Sets the custom crosstalk vector table to a specified device. * * @details The crosstalk vectors are subtracted from the raw sampling data - * in the data evaluation phase. + * in the data evaluation phase. * - * The crosstalk vector table is a three dimensional array of type - * #xtalk_t. + * The crosstalk vector table is a three dimensional array of type + * #xtalk_t. The #argus_cal_xtalk_table_t is the corresponding + * typedef for the required data. * - * The dimensions are: - * - size(0) = #ARGUS_DFM_FRAME_COUNT (Dual-frequency mode A- or B-frame) - * - size(1) = #ARGUS_PIXELS_X (Pixel count in x-direction) - * - size(2) = #ARGUS_PIXELS_Y (Pixel count in y-direction) - * . + * The dimensions are: + * - size(0) = #ARGUS_DFM_FRAME_COUNT (Dual-frequency mode A- or B-frame) + * - size(1) = #ARGUS_PIXELS_X (Pixel count in x-direction) + * - size(2) = #ARGUS_PIXELS_Y (Pixel count in y-direction) + * . * - * Its recommended to use the built-in crosstalk calibration sequence - * (see #Argus_ExecuteXtalkCalibrationSequence) to determine the - * crosstalk vector table. + * Its recommended to use the built-in crosstalk calibration sequence + * (see #Argus_ExecuteXtalkCalibrationSequence) to determine the + * crosstalk vector table. * - * If a constant table for all device needs to be incorporated into - * the sources, the #Argus_GetExternalCrosstalkVectorTable_Callback - * should be used. + * If a constant table for all device needs to be incorporated into + * the sources, the #Argus_GetCrosstalkVectorTable_Callback + * should be used. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The new crosstalk vector table. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The new crosstalk vector table. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetCalibrationCrosstalkVectorTable(argus_hnd_t *hnd, - argus_mode_t mode, - xtalk_t value[ARGUS_DFM_FRAME_COUNT][ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); + argus_cal_xtalk_table_t const *value); /*!*************************************************************************** - * @brief Gets the custom crosstalk vector table from a specified device. + * @brief Gets the custom crosstalk vector table from a specified device. * * @details The crosstalk vectors are subtracted from the raw sampling data - * in the data evaluation phase. + * in the data evaluation phase. * - * The crosstalk vector table is a three dimensional array of type - * #xtalk_t. + * The crosstalk vector table is a three dimensional array of type + * #xtalk_t. The #argus_cal_xtalk_table_t is the corresponding + * typedef for the required data. * - * The dimensions are: - * - size(0) = #ARGUS_DFM_FRAME_COUNT (Dual-frequency mode A- or B-frame) - * - size(1) = #ARGUS_PIXELS_X (Pixel count in x-direction) - * - size(2) = #ARGUS_PIXELS_Y (Pixel count in y-direction) - * . + * The dimensions are: + * - size(0) = #ARGUS_DFM_FRAME_COUNT (Dual-frequency mode A- or B-frame) + * - size(1) = #ARGUS_PIXELS_X (Pixel count in x-direction) + * - size(2) = #ARGUS_PIXELS_Y (Pixel count in y-direction) + * . * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current crosstalk vector table. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current crosstalk vector table. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetCalibrationCrosstalkVectorTable(argus_hnd_t *hnd, - argus_mode_t mode, - xtalk_t value[ARGUS_DFM_FRAME_COUNT][ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); + argus_cal_xtalk_table_t *value); /*!*************************************************************************** - * @brief Gets the factory calibrated default crosstalk vector table for the - * specified device. + * @brief Resets the crosstalk vector table for the specified device to the + * factory calibrated default values. * * @details The crosstalk vectors are subtracted from the raw sampling data - * in the data evaluation phase. - * - * The crosstalk vector table is a three dimensional array of type - * #xtalk_t. - * - * The dimensions are: - * - size(0) = #ARGUS_DFM_FRAME_COUNT (Dual-frequency mode A- or B-frame) - * - size(1) = #ARGUS_PIXELS_X (Pixel count in x-direction) - * - size(2) = #ARGUS_PIXELS_Y (Pixel count in y-direction) - * . - * - * The total vector table consists of the custom crosstalk vector - * table (set via #Argus_SetCalibrationCrosstalkVectorTable) and - * an internal, factory calibrated device specific vector table. - * This is informational only! - * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @param value The current total crosstalk vector table. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). - *****************************************************************************/ -status_t Argus_GetCalibrationTotalCrosstalkVectorTable(argus_hnd_t *hnd, - argus_mode_t mode, - xtalk_t value[ARGUS_DFM_FRAME_COUNT][ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); - -/*!*************************************************************************** - * @brief Resets the crosstalk vector table for the specified device to the - * factory calibrated default values. - * - * @details The crosstalk vectors are subtracted from the raw sampling data - * in the data evaluation phase. + * in the data evaluation phase. * * - * The factory defaults are device specific calibrated values. + * The factory defaults are device specific calibrated values. * - * @param hnd The API handle; contains all internal states and data. - * @param mode The targeted measurement mode. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_ResetCalibrationCrosstalkVectorTable(argus_hnd_t *hnd, - argus_mode_t mode); +status_t Argus_ResetCalibrationCrosstalkVectorTable(argus_hnd_t *hnd); /*!*************************************************************************** - * @brief Sets the sample time for the crosstalk calibration sequence. + * @brief Sets the sample time for the crosstalk calibration sequence. * - * @details Sets the measurement sample acquisition time for executing the - * crosstalk calibration sequence and generate the crosstalk data.\n - * Units: msec. + * @details Sets the measurement sample acquisition time for executing the + * crosstalk calibration sequence and generate the crosstalk data.\n + * Units: msec. * - * @param hnd The API handle; contains all internal states and data. - * @param value The new crosstalk calibration sequence sample time. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The new crosstalk calibration sequence sample time. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetCalibrationCrosstalkSequenceSampleTime(argus_hnd_t *hnd, uint16_t value); /*!*************************************************************************** - * @brief Gets the sample time for the crosstalk calibration sequence. + * @brief Gets the sample time for the crosstalk calibration sequence. * - * @details Gets the measurement sample acquisition time for executing the - * crosstalk calibration sequence and generate the crosstalk data.\n - * Units: msec. + * @details Gets the measurement sample acquisition time for executing the + * crosstalk calibration sequence and generate the crosstalk data.\n + * Units: msec. * - * @param hnd The API handle; contains all internal states and data. - * @param value The current crosstalk calibration sequence sample time. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current crosstalk calibration sequence sample time. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetCalibrationCrosstalkSequenceSampleTime(argus_hnd_t *hnd, uint16_t *value); /*!*************************************************************************** - * @brief Sets the max. amplitude threshold for the crosstalk calibration sequence. + * @brief Sets the max. amplitude threshold for the crosstalk calibration sequence. * - * @details The maximum amplitude threshold defines a maximum crosstalk vector - * amplitude before causing an error message. If the crosstalk is - * too high, there is usually an issue with the measurement setup, i.e. - * there is still a measurement signal detected. + * @details The maximum amplitude threshold defines a maximum crosstalk vector + * amplitude before causing an error message. If the crosstalk is + * too high, there is usually an issue with the measurement setup, i.e. + * there is still a measurement signal detected. * - * @param hnd The API handle; contains all internal states and data. - * @param value The new crosstalk calibration sequence maximum amplitude - * threshold value in UQ12.4 format. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The new crosstalk calibration sequence maximum amplitude + * threshold value in UQ12.4 format. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_SetCalibrationCrosstalkSequenceAmplitudeThreshold(argus_hnd_t *hnd, uq12_4_t value); /*!*************************************************************************** - * @brief Gets the max. amplitude threshold for the crosstalk calibration sequence. + * @brief Gets the max. amplitude threshold for the crosstalk calibration sequence. * - * @details The maximum amplitude threshold defines a maximum crosstalk vector - * amplitude before causing an error message. If the crosstalk is - * too high, there is usually an issue with the measurement setup, i.e. - * there is still a measurement signal detected. + * @details The maximum amplitude threshold defines a maximum crosstalk vector + * amplitude before causing an error message. If the crosstalk is + * too high, there is usually an issue with the measurement setup, i.e. + * there is still a measurement signal detected. * - * @param hnd The API handle; contains all internal states and data. - * @param value The current max. amplitude threshold value in UQ12.4 format. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param hnd The API handle; contains all internal states and data. + * @param value The current max. amplitude threshold value in UQ12.4 format. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_GetCalibrationCrosstalkSequenceAmplitudeThreshold(argus_hnd_t *hnd, uq12_4_t *value); -/*!*************************************************************************** - * @brief Sets the sample count for the substrate voltage calibration sequence. - * - * @param hnd The API handle; contains all internal states and data. - * @param value The new substrate voltage calibration sequence sample count. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). - *****************************************************************************/ -status_t Argus_SetCalibrationVsubSequenceSampleCount(argus_hnd_t *hnd, - uint16_t value); /*!*************************************************************************** - * @brief Gets the sample count for the substrate voltage calibration sequence. + * @brief Clears all user calibration values from NVM for the specified device. * - * @param hnd The API handle; contains all internal states and data. - * @param value The current substrate voltage calibration sequence sample count. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @details The user calibration values are stored in the non-volatile memory + * (NVM) if corresponding \link #argus_nvm NVM hardware layer\endlink + * is implemented. This method clears the user calibration data from + * the non-volatile memory. + * + * @warning This does not reset the currently set calibration values to + * factory defaults! + * + * @param hnd The API handle; contains all internal states and data. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t Argus_GetCalibrationVsubSequenceSampleCount(argus_hnd_t *hnd, - uint16_t *value); +status_t Argus_ClearUserCalibration(argus_hnd_t *hnd); + /*!*************************************************************************** - * @brief A callback that returns the external crosstalk vector table. + * @brief A callback that returns the external crosstalk vector table. * * @details The function needs to be implemented by the host application in - * order to set the external crosstalk vector table upon system - * initialization. If not defined in user code, the default - * implementation will return an all zero vector table, assuming there - * is no (additional) external crosstalk. + * order to set the external crosstalk vector table upon system + * initialization. If not defined in user code, the default + * implementation will return an all zero vector table, assuming there + * is no (additional) external crosstalk. * - * If defined in user code, the function must fill all vector values - * in the provided \par xtalk parameter with external crosstalk values. - * The values can be obtained by the calibration routine. + * If defined in user code, the function must fill all vector values + * in the provided \par crosstalk parameter with external crosstalk + * values. The values can be obtained by the calibration routine. * - * Example usage: + * Example usage: * - * @code - * status_t Argus_GetExternalCrosstalkVectorTable_Callback(xtalk_t xtalk[ARGUS_DFM_FRAME_COUNT][ARGUS_PIXELS_X][ARGUS_PIXELS_Y], - * argus_mode_t mode) - * { - * (void) mode; // Ignore mode; use same values for all modes. - * memset(&xtalk, 0, sizeof(xtalk)); + * @code + * status_t Argus_GetCrosstalkVectorTable_Callback( + * argus_cal_xtalk_table_t * xtalk) + * { + * memset(xtalk, 0, sizeof(argus_cal_xtalk_table_t)); * - * // Set crosstalk vectors in Q11.4 format. - * // Note on dual-frequency frame index: 0 = A-Frame; 1 = B-Frame - * xtalk[0][0][0].dS = -9; xtalk[0][0][0].dC = -11; - * xtalk[0][0][1].dS = -13; xtalk[0][0][1].dC = -16; - * xtalk[0][0][2].dS = 6; xtalk[0][0][2].dC = -18; - * // etc. - * } - * @endcode + * // Set crosstalk vectors in Q11.4 format. + * // Note on dual-frequency frame index: 0 = A-Frame; 1 = B-Frame + * xtalk.FrameA[0][0].dS = -9; xtalk.FrameB[0][0].dC = -11; + * xtalk.FrameA[0][1].dS = -13; xtalk.FrameB[0][1].dC = -16; + * xtalk.FrameA[0][2].dS = 6; xtalk.FrameB[0][2].dC = -18; + * // etc. + * } + * @endcode * - * @param xtalk The crosstalk vector array; to be filled with data. - * @param mode Determines the current measurement mode; can be ignored if - * only a single measurement mode is utilized. + * @param xtalk The crosstalk vector array; to be filled with data. + * @param mode The current measurement mode. *****************************************************************************/ -void Argus_GetExternalCrosstalkVectorTable_Callback(xtalk_t - xtalk[ARGUS_DFM_FRAME_COUNT][ARGUS_PIXELS_X][ARGUS_PIXELS_Y], - argus_mode_t mode); +void Argus_GetCrosstalkVectorTable_Callback(argus_cal_xtalk_table_t *xtalk, + argus_mode_t const mode); -#ifdef __cplusplus -} -#endif +/*!*************************************************************************** + * @brief Gets the currently calibrated Golden Pixel coordinates. + * + * @details The Golden Pixel is the pixel that is located at the center of the + * receiving light beam. Thus it it the one that receives the most + * signal and plays a central role in 1D measurement systems. + * + * The function fills the provided \p x and \p y parameters with + * the Golden Pixel coordinates. Typical values are x = 5 and y = 1 + * or 2. But the actual values depend on the specific sensor. + * + * Please also note the utility functions provided in the \ref argus_map + * module to convert between pixel coordinates and channel numbers or + * shift pixel maps by a position offset (#ShiftSelectedPixels) or + * generate pixel masks centered around the Golden Pixel + * (#FillPixelMask). + * + * @param hnd The API handle; contains all internal states and data. + * @param x The Golden Pixel x-coordinate. + * @param y The Golden Pixel y-coordinate. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_GetCalibrationGoldenPixel(argus_hnd_t const *hnd, uint8_t *x, uint8_t *y); /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_API_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dca.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dca.h index f23d117648..8f6b40bdc5 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dca.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dca.h @@ -1,12 +1,12 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Defines the dynamic configuration adaption (DCA) setup parameters - * and data structure. + * @brief This file is part of the AFBR-S50 API. + * @details Defines the dynamic configuration adaption (DCA) setup parameters + * and data structure. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,34 +37,37 @@ #ifndef ARGUS_DCA_H #define ARGUS_DCA_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup argusdca Dynamic Configuration Adaption - * @ingroup argusapi + * @defgroup argus_dca Dynamic Configuration Adaption + * @ingroup argus_api * - * @brief Dynamic Configuration Adaption (DCA) parameter definitions and API functions. + * @brief Dynamic Configuration Adaption (DCA) parameter definitions and API functions. * - * @details The DCA contains an algorithms that detect ambient conditions - * and adopt the device configuration to the changing parameters - * dynamically while operating the sensor. This is achieved by - * rating the currently received signal quality and changing the - * device configuration accordingly to the gathered information - * from the current measurement frame results before the next - * integration cycle starts. + * @details The DCA contains an algorithms that detect ambient conditions + * and adopt the device configuration to the changing parameters + * dynamically while operating the sensor. This is achieved by + * rating the currently received signal quality and changing the + * device configuration accordingly to the gathered information + * from the current measurement frame results before the next + * integration cycle starts. * - * The DCA consists of the following features: - * - Static or Dynamic mode. The first is utilizing the nominal - * values while the latter is dynamically adopting between min. - * and max. value and starting from the nominal values. - * - Analog Integration Depth Adaption (from multiple patterns down to single pulses) - * - Optical Output Power Adaption - * - Pixel Input Gain Adaption (w/ ambient light rejection) - * - ADC Sensitivity (i.e. ADC Range) Adaption - * - Power Saving Ratio (to decrease the average output power and thus the current consumption) - * - All that features are heeding the Laser Safety limits. - * . + * The DCA consists of the following features: + * - Static or Dynamic mode. The first is utilizing the nominal + * values while the latter is dynamically adopting between min. + * and max. value and starting from the nominal values. + * - Analog Integration Depth Adaption (from multiple patterns down to single pulses) + * - Optical Output Power Adaption + * - Pixel Input Gain Adaption (w/ ambient light rejection) + * - ADC Sensitivity (i.e. ADC Range) Adaption + * - Power Saving Ratio (to decrease the average output power and thus the current consumption) + * - All that features are heeding the Laser Safety limits. + * . * - * @addtogroup argusdca + * @addtogroup argus_dca * @{ *****************************************************************************/ @@ -73,39 +76,26 @@ /*! The minimum amplitude threshold value. */ -#define ARGUS_CFG_DCA_ATH_MIN (1U << 6U) +#define ARGUS_CFG_DCA_ATH_MIN (1U << 6U) /*! The maximum amplitude threshold value. */ -#define ARGUS_CFG_DCA_ATH_MAX (0xFFFFU) +#define ARGUS_CFG_DCA_ATH_MAX (0xFFFFU) /*! The minimum saturated pixel threshold value. */ -#define ARGUS_CFG_DCA_PXTH_MIN (1U) +#define ARGUS_CFG_DCA_PXTH_MIN (1U) /*! The maximum saturated pixel threshold value. */ -#define ARGUS_CFG_DCA_PXTH_MAX (33U) +#define ARGUS_CFG_DCA_PXTH_MAX (33U) /*! The maximum analog integration depth in UQ10.6 format, * i.e. the maximum pattern count per sample. */ -#define ARGUS_CFG_DCA_DEPTH_MAX ((uq10_6_t)(ADS_SEQCT_N_MASK << (6U - ADS_SEQCT_N_SHIFT))) +#define ARGUS_CFG_DCA_DEPTH_MAX ((uq10_6_t)(0xFFC0U)) /*! The minimum analog integration depth in UQ10.6 format, * i.e. the minimum pattern count per sample. */ -#define ARGUS_CFG_DCA_DEPTH_MIN ((uq10_6_t)(1U)) // 1/64, i.e. 1/2 nibble - - -/*! The maximum optical output power, i.e. the maximum VCSEL high current in LSB. */ -#define ARGUS_CFG_DCA_POWER_MAX_LSB (ADS_LASET_VCSEL_HC1_MASK >> ADS_LASET_VCSEL_HC1_SHIFT) - -/*! The minimum optical output power, i.e. the minimum VCSEL high current in mA. */ -#define ARGUS_CFG_DCA_POWER_MIN_LSB (1) - -/*! The maximum optical output power, i.e. the maximum VCSEL high current in LSB. */ -#define ARGUS_CFG_DCA_POWER_MAX (ADS0032_HIGH_CURRENT_LSB2MA(ARGUS_CFG_DCA_POWER_MAX_LSB + 1)) - -/*! The minimum optical output power, i.e. the minimum VCSEL high current in mA. */ -#define ARGUS_CFG_DCA_POWER_MIN (1) +#define ARGUS_CFG_DCA_DEPTH_MIN ((uq10_6_t)(1U)) // 1/64, i.e. 1/2 nibble /*! The dynamic configuration algorithm Pixel Input Gain stage count. */ @@ -139,9 +129,9 @@ /*!*************************************************************************** - * @brief The dynamic configuration algorithm enable flags. + * @brief The dynamic configuration algorithm enable flags. *****************************************************************************/ -typedef enum { +typedef enum argus_dca_enable_t { /*! @internal * * DCA is disabled and will be completely skipped. @@ -160,9 +150,9 @@ typedef enum { } argus_dca_enable_t; /*!*************************************************************************** - * @brief The DCA amplitude evaluation method. + * @brief The DCA amplitude evaluation method. *****************************************************************************/ -typedef enum { +typedef enum argus_dca_amplitude_mode_t { /*! Evaluate the DCA amplitude as the maximum of all valid amplitudes. */ DCA_AMPLITUDE_MAX = 1U, @@ -172,9 +162,9 @@ typedef enum { } argus_dca_amplitude_mode_t; /*!*************************************************************************** - * @brief The dynamic configuration algorithm Optical Output Power stages enumerator. + * @brief The dynamic configuration algorithm Optical Output Power stages enumerator. *****************************************************************************/ -typedef enum { +typedef enum argus_dca_power_t { /*! Use low output power stage. */ DCA_POWER_LOW = 0, @@ -187,9 +177,9 @@ typedef enum { } argus_dca_power_t; /*!*************************************************************************** - * @brief The dynamic configuration algorithm Pixel Input Gain stages enumerator. + * @brief The dynamic configuration algorithm Pixel Input Gain stages enumerator. *****************************************************************************/ -typedef enum { +typedef enum argus_dca_gain_t { /*! Low gain stage. */ DCA_GAIN_LOW = 0, @@ -206,113 +196,113 @@ typedef enum { /*!*************************************************************************** - * @brief State flags for the current frame. - * @details State flags determine the current state of the measurement frame: - * - [0]: #ARGUS_STATE_MEASUREMENT_MODE - * - [1]: #ARGUS_STATE_DUAL_FREQ_MODE - * - [2]: #ARGUS_STATE_MEASUREMENT_FREQ - * - [3]: #ARGUS_STATE_DEBUG_MODE - * - [4]: #ARGUS_STATE_WEAK_SIGNAL - * - [5]: #ARGUS_STATE_BGL_WARNING - * - [6]: #ARGUS_STATE_BGL_ERROR - * - [7]: #ARGUS_STATE_PLL_LOCKED - * - [8]: #ARGUS_STATE_LASER_WARNING - * - [9]: #ARGUS_STATE_LASER_ERROR - * - [10]: #ARGUS_STATE_HAS_DATA - * - [11]: #ARGUS_STATE_HAS_AUX_DATA - * - [12]: #ARGUS_STATE_DCA_MAX - * - [13]: DCA Power Stage - * - [14-15]: DCA Gain Stages - * . + * @brief State flags for the current frame. + * @details State flags determine the current state of the measurement frame: + * - [0]: #ARGUS_STATE_XTALK_MONITOR_ACTIVE + * - [1]: #ARGUS_STATE_DUAL_FREQ_MODE + * - [2]: #ARGUS_STATE_MEASUREMENT_FREQ + * - [3]: #ARGUS_STATE_DEBUG_MODE + * - [4]: #ARGUS_STATE_WEAK_SIGNAL + * - [5]: #ARGUS_STATE_BGL_WARNING + * - [6]: #ARGUS_STATE_BGL_ERROR + * - [7]: #ARGUS_STATE_PLL_LOCKED + * - [8]: #ARGUS_STATE_LASER_WARNING + * - [9]: #ARGUS_STATE_LASER_ERROR + * - [10]: #ARGUS_STATE_HAS_DATA + * - [11]: #ARGUS_STATE_HAS_AUX_DATA + * - [12]: #ARGUS_STATE_DCA_MAX + * - [13]: DCA Power Stage + * - [14-15]: DCA Gain Stages + * . *****************************************************************************/ -typedef enum { +typedef enum argus_state_t { /*! No state flag set. */ ARGUS_STATE_NONE = 0, - /*! 0x0001: Measurement Mode. - * - 0: Mode A: Long Range / Medium Precision - * - 1: Mode B: Short Range / High Precision */ - ARGUS_STATE_MEASUREMENT_MODE = 1U << 0U, + /*! 0x0001: Crosstalk Monitor is enabled and updating. + * - 0: Inactive: crosstalk monitor values are not updated, + * - 1: Active: crosstalk monitor values are updated. */ + ARGUS_STATE_XTALK_MONITOR_ACTIVE = 1U << 0U, /*! 0x0002: Dual Frequency Mode Enabled. - * - 0: Disabled: measurements with base frequency, - * - 1: Enabled: measurement with detuned frequency. */ + * - 0: Disabled: measurements with base frequency, + * - 1: Enabled: measurement with detuned frequency. */ ARGUS_STATE_DUAL_FREQ_MODE = 1U << 1U, /*! 0x0004: Measurement Frequency for Dual Frequency Mode * (only if #ARGUS_STATE_DUAL_FREQ_MODE flag is set). - * - 0: A-Frame w/ detuned frequency, - * - 1: B-Frame w/ detuned frequency */ + * - 0: A-Frame w/ detuned frequency, + * - 1: B-Frame w/ detuned frequency */ ARGUS_STATE_MEASUREMENT_FREQ = 1U << 2U, /*! 0x0008: Debug Mode. If set, the range value of erroneous pixels - * are not cleared or reset. - * - 0: Disabled (default). - * - 1: Enabled. */ + * are not cleared or reset. + * - 0: Disabled (default). + * - 1: Enabled. */ ARGUS_STATE_DEBUG_MODE = 1U << 3U, /*! 0x0010: Weak Signal Flag. - * Set whenever the Pixel Binning Algorithm is detecting a - * weak signal, i.e. if the amplitude dies not reach its - * (absolute) threshold. If the Golden Pixel is enabled, - * this also indicates that the Pixel Binning Algorithm - * falls back to the Golden Pixel. - * - 0: Normal Signal. - * - 1: Weak Signal or Golden Pixel Mode. */ + * Set whenever the Pixel Binning Algorithm is detecting a + * weak signal, i.e. if the amplitude dies not reach its + * (absolute) threshold. If the Golden Pixel is enabled, + * this also indicates that the Pixel Binning Algorithm + * falls back to the Golden Pixel. + * - 0: Normal Signal. + * - 1: Weak Signal or Golden Pixel Mode. */ ARGUS_STATE_WEAK_SIGNAL = 1U << 4U, /*! 0x0020: Background Light Warning Flag. - * Set whenever the background light is very high and the - * measurement data might be unreliable. - * - 0: No Warning: Background Light is within valid range. - * - 1: Warning: Background Light is very high. */ + * Set whenever the background light is very high and the + * measurement data might be unreliable. + * - 0: No Warning: Background Light is within valid range. + * - 1: Warning: Background Light is very high. */ ARGUS_STATE_BGL_WARNING = 1U << 5U, /*! 0x0040: Background Light Error Flag. - * Set whenever the background light is too high and the - * measurement data is unreliable or invalid. - * - 0: No Error: Background Light is within valid range. - * - 1: Error: Background Light is too high. */ + * Set whenever the background light is too high and the + * measurement data is unreliable or invalid. + * - 0: No Error: Background Light is within valid range. + * - 1: Error: Background Light is too high. */ ARGUS_STATE_BGL_ERROR = 1U << 6U, /*! 0x0080: PLL_LOCKED bit. - * - 0: PLL not locked at start of integration. - * - 1: PLL locked at start of integration. */ + * - 0: PLL not locked at start of integration. + * - 1: PLL locked at start of integration. */ ARGUS_STATE_PLL_LOCKED = 1U << 7U, /*! 0x0100: Laser Failure Warning Flag. - * Set whenever the an invalid system condition is detected. - * (i.e. DCA at max state but no amplitude on any (incl. reference) - * pixel, not amplitude but any saturated pixel). - * - 0: No Warning: Laser is operating properly. - * - 1: Warning: Invalid laser conditions detected. If the invalid - * condition stays, a laser malfunction error is raised. */ + * Set whenever the an invalid system condition is detected. + * (i.e. DCA at max state but no amplitude on any (incl. reference) + * pixel, not amplitude but any saturated pixel). + * - 0: No Warning: Laser is operating properly. + * - 1: Warning: Invalid laser conditions detected. If the invalid + * condition stays, a laser malfunction error is raised. */ ARGUS_STATE_LASER_WARNING = 1U << 8U, /*! 0x0200: Laser Failure Error Flag. - * Set whenever a laser malfunction error is raised and the - * system is put into a safe state. - * - 0: No Error: Laser is operating properly. - * - 1: Error: Invalid laser conditions are detected for a certain - * soak time and the system is put into a safe state. */ + * Set whenever a laser malfunction error is raised and the + * system is put into a safe state. + * - 0: No Error: Laser is operating properly. + * - 1: Error: Invalid laser conditions are detected for a certain + * soak time and the system is put into a safe state. */ ARGUS_STATE_LASER_ERROR = 1U << 9U, /*! 0x0400: Set if current frame has distance measurement data available. - * - 0: No measurement data available, all values are 0 or stalled. - * - 1: Measurement data is available and correctly evaluated. */ + * - 0: No measurement data available, all values are 0 or stalled. + * - 1: Measurement data is available and correctly evaluated. */ ARGUS_STATE_HAS_DATA = 1U << 10U, /*! 0x0800: Set if current frame has auxiliary measurement data available. - * - 0: No auxiliary data available, all values are 0 or stalled. - * - 1: Auxiliary data is available and correctly evaluated. */ + * - 0: No auxiliary data available, all values are 0 or stalled. + * - 1: Auxiliary data is available and correctly evaluated. */ ARGUS_STATE_HAS_AUX_DATA = 1U << 11U, /*! 0x1000: DCA Maximum State Flag. - * Set whenever the DCA has extended all its parameters to their - * maximum values and can not increase the integration energy any - * further. - * - 0: DCA has not yet reached its maximum state. - * - 1: DCA has reached its maximum state and can not increase any further. */ + * Set whenever the DCA has extended all its parameters to their + * maximum values and can not increase the integration energy any + * further. + * - 0: DCA has not yet reached its maximum state. + * - 1: DCA has reached its maximum state and can not increase any further. */ ARGUS_STATE_DCA_MAX = 1U << 12U, /*! 0x2000: DCA is in high Optical Output Power stage. */ @@ -333,20 +323,20 @@ typedef enum { } argus_state_t; /*!*************************************************************************** - * @brief Dynamic Configuration Adaption (DCA) Parameters. - * @details DCA contains: - * - Static or dynamic mode. The first is utilizing the nominal values - * while the latter is dynamically adopting between min. and max. - * value and starting form the nominal values. - * - Analog Integration Depth Adaption down to single pulses. - * - Optical Output Power Adaption - * - Pixel Input Gain Adaption - * - Digital Integration Depth Adaption - * - Dynamic Global Phase Shift Injection. - * - All that features are heeding the Laser Safety limits. - * . + * @brief Dynamic Configuration Adaption (DCA) Parameters. + * @details DCA contains: + * - Static or dynamic mode. The first is utilizing the nominal values + * while the latter is dynamically adopting between min. and max. + * value and starting form the nominal values. + * - Analog Integration Depth Adaption down to single pulses. + * - Optical Output Power Adaption + * - Pixel Input Gain Adaption + * - Digital Integration Depth Adaption + * - Dynamic Global Phase Shift Injection. + * - All that features are heeding the Laser Safety limits. + * . *****************************************************************************/ -typedef struct { +typedef struct argus_cfg_dca_t { /*! Enables the automatic configuration adaption features. * Enables the dynamic part if #DCA_ENABLE_DYNAMIC and the static only if * #DCA_ENABLE_STATIC. */ @@ -494,4 +484,7 @@ typedef struct { } argus_cfg_dca_t; /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_DCA_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_def.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_def.h index c639922a7d..6d9dd3e9d3 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_def.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_def.h @@ -1,12 +1,12 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 hardware API. - * @details This file provides generic definitions belonging to all - * devices from the AFBR-S50 product family. + * @brief This file is part of the AFBR-S50 hardware API. + * @details This file provides generic definitions belonging to all + * devices from the AFBR-S50 product family. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,6 +37,9 @@ #ifndef ARGUS_DEF_H #define ARGUS_DEF_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** * Include files @@ -52,36 +55,41 @@ #include /*!*************************************************************************** - * @addtogroup argusapi + * @addtogroup argus_api * @{ *****************************************************************************/ /*!*************************************************************************** - * @brief Maximum number of phases per measurement cycle. - * @details The actual phase number is defined in the register configuration. - * However the software does only support a fixed value of 4 yet. + * @brief Maximum number of phases per measurement cycle. + * @details The actual phase number is defined in the register configuration. + * However the software does only support a fixed value of 4 yet. *****************************************************************************/ -#define ARGUS_PHASECOUNT 4U +#define ARGUS_PHASECOUNT 4 /*!*************************************************************************** - * @brief The device pixel field size in x direction (long edge). + * @brief The device pixel field size in x direction (long edge). *****************************************************************************/ -#define ARGUS_PIXELS_X 8U +#define ARGUS_PIXELS_X 8 /*!*************************************************************************** - * @brief The device pixel field size in y direction (short edge). + * @brief The device pixel field size in y direction (short edge). *****************************************************************************/ -#define ARGUS_PIXELS_Y 4U +#define ARGUS_PIXELS_Y 4 /*!*************************************************************************** - * @brief The total device pixel count. + * @brief The total device pixel count. *****************************************************************************/ -#define ARGUS_PIXELS ((ARGUS_PIXELS_X)*(ARGUS_PIXELS_Y)) +#define ARGUS_PIXELS ((ARGUS_PIXELS_X)*(ARGUS_PIXELS_Y)) /*!*************************************************************************** - * @brief The AFBR-S50 module types. + * @brief A flag indicating that the device is a extended range device. *****************************************************************************/ -typedef enum { +#define MODULE_EXTENDED_FLAG (0x40U) + +/*!*************************************************************************** + * @brief The AFBR-S50 module types. + *****************************************************************************/ +typedef enum argus_module_version_t { /*! No device connected or not recognized. */ MODULE_NONE = 0, @@ -89,54 +97,80 @@ typedef enum { * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for * medium range 3D applications. * Version 1 - legacy version! */ - AFBR_S50MV85G_V1 = 1, + AFBR_S50MV85G_V1 = 0x01, /*! AFBR-S50MV85G: an ADS0032 based multi-pixel range finder device * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for * medium range 3D applications. * Version 2 - legacy version! */ - AFBR_S50MV85G_V2 = 2, - - /*! AFBR-S50MV85G: an ADS0032 based multi-pixel range finder device - * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for - * medium range 3D applications. - * Version 7 - current version! */ - AFBR_S50MV85G_V3 = 7, + AFBR_S50MV85G_V2 = 0x02, /*! AFBR-S50LV85D: an ADS0032 based multi-pixel range finder device * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for * long range 1D applications. * Version 1 - current version! */ - AFBR_S50LV85D_V1 = 3, + AFBR_S50LV85D_V1 = 0x03, /*! AFBR-S50MV68B: an ADS0032 based multi-pixel range finder device * w/ 4x8 pixel matrix and red, 680 nm, laser source for * medium range 1D applications. * Version 1 - current version! */ - AFBR_S50MV68B_V1 = 4, + AFBR_S50MV68B_V1 = 0x04, /*! AFBR-S50MV85I: an ADS0032 based multi-pixel range finder device * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for * medium range 3D applications. * Version 1 - current version! */ - AFBR_S50MV85I_V1 = 5, + AFBR_S50MV85I_V1 = 0x05, /*! AFBR-S50MV85G: an ADS0032 based multi-pixel range finder device * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for * short range 3D applications. * Version 1 - current version! */ - AFBR_S50SV85K_V1 = 6, + AFBR_S50SV85K_V1 = 0x06, + /*! AFBR-S50MV85G: an ADS0032 based multi-pixel range finder device + * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for + * medium range 3D applications. + * Version 3 - current version! */ + AFBR_S50MV85G_V3 = 0x07, - /*! Reserved for future extensions. */ - Reserved = 0x3F + /*! AFBR-S50LX85D: an ADS0032 based multi-pixel range finder device + * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for + * extended long range 1D applications. + * Version 1 - current version! */ + AFBR_S50LX85D_V1 = AFBR_S50LV85D_V1 | MODULE_EXTENDED_FLAG, + + /*! AFBR-S50MX68B: an ADS0032 based multi-pixel range finder device + * w/ 4x8 pixel matrix and red, 680 nm, laser source for + * extended medium range 1D applications. + * Version 1 - current version! */ + AFBR_S50MX68B_V1 = AFBR_S50MV68B_V1 | MODULE_EXTENDED_FLAG, + + /*! AFBR-S50MX85I: an ADS0032 based multi-pixel range finder device + * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for + * extended medium range 3D applications. + * Version 1 - current version! */ + AFBR_S50MX85I_V1 = AFBR_S50MV85I_V1 | MODULE_EXTENDED_FLAG, + + /*! AFBR-S50MX85G: an ADS0032 based multi-pixel range finder device + * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for + * extended short range 3D applications. + * Version 1 - current version! */ + AFBR_S50SX85K_V1 = AFBR_S50SV85K_V1 | MODULE_EXTENDED_FLAG, + + /*! AFBR-S50MX85G: an ADS0032 based multi-pixel range finder device + * w/ 4x8 pixel matrix and infra-red, 850 nm, laser source for + * extended medium range 3D applications. + * Version 1 - current version! */ + AFBR_S50MX85G_V1 = AFBR_S50MV85G_V3 | MODULE_EXTENDED_FLAG, } argus_module_version_t; /*!*************************************************************************** - * @brief The AFBR-S50 laser configurations. + * @brief The AFBR-S50 laser configurations. *****************************************************************************/ -typedef enum { +typedef enum argus_laser_type_t { /*! No laser connected. */ LASER_NONE = 0, @@ -152,12 +186,15 @@ typedef enum { /*! 850nm Infra-Red VCSEL v2 /w extended mode. */ LASER_H_V2X = 4, + /*! 680nm Red VCSEL v1 w/ extended mode. */ + LASER_R_V1X = 5, + } argus_laser_type_t; /*!*************************************************************************** - * @brief The AFBR-S50 chip versions. + * @brief The AFBR-S50 chip versions. *****************************************************************************/ -typedef enum { +typedef enum argus_chip_version_t { /*! No device connected or not recognized. */ ADS0032_NONE = 0, @@ -178,37 +215,102 @@ typedef enum { } argus_chip_version_t; -/*!*************************************************************************** - * @brief The number of measurement modes with distinct configuration and - * calibration records. - *****************************************************************************/ -#define ARGUS_MODE_COUNT (2) /*!*************************************************************************** - * @brief The measurement modes. + * @brief The measurement mode flags. + * @details The measurement mode flags that can be combined to a measurement + * mode, e.g. high speed short range mode. See #argus_mode_t for + * a complete list of available measurement modes. + * + * - Bit 0: Short Range Mode + * - Bit 1: Long Range Mode + * - Bit 2: High Speed Mode + * + * Note that the Long and Short Range Flags are mutual exclusive but + * any of those 2 must be set. Thus the value 0 is invalid! + * All other flags enhance the base configurations, e.g. the High + * Speed flag create the high speed mode of the selected base + * measurement mode. *****************************************************************************/ -typedef enum { - /*! Measurement Mode A: Long Range Mode. */ - ARGUS_MODE_A = 1, +typedef enum argus_mode_flags_t { + /*! Measurement Mode Flag for Short Range Base Mode. */ + ARGUS_MODE_FLAG_SHORT_RANGE = 0x01 << 0, - /*! Measurement Mode B: Short Range Mode. */ - ARGUS_MODE_B = 2, + /*! Measurement Mode Flag for Long Range Base Mode. */ + ARGUS_MODE_FLAG_LONG_RANGE = 0x01 << 1, + + /*! Measurement Mode Flag for High Speed Mode. */ + ARGUS_MODE_FLAG_HIGH_SPEED = 0x01 << 2 + +} argus_mode_flags_t; + +/*!*************************************************************************** + * @brief The measurement modes. + * @details The measurement modes are composed in binary from of the flags + * define in #argus_mode_flags_t, i.e. each bit has a special meaning: + * + * - Bit 0: Short Range Mode + * - Bit 1: Long Range Mode + * - Bit 2: High Speed Mode + * + * Note that the Long and Short Range Bits are mutual exclusive but any + * of those 2 must be set. Thus the value 0 is invalid! + *****************************************************************************/ +typedef enum argus_mode_t { + /*! Measurement Mode: Short Range Mode. */ + ARGUS_MODE_SHORT_RANGE = // = 0x01 = 0b0001 + ARGUS_MODE_FLAG_SHORT_RANGE, + + /*! Measurement Mode: Long Range Mode. */ + ARGUS_MODE_LONG_RANGE = // = 0x02 = 0b0010 + ARGUS_MODE_FLAG_LONG_RANGE, + + /*! Measurement Mode: High Speed Short Range Mode. */ + ARGUS_MODE_HIGH_SPEED_SHORT_RANGE = // = 0x05 = 0b0101 + ARGUS_MODE_FLAG_SHORT_RANGE | ARGUS_MODE_FLAG_HIGH_SPEED, + + /*! Measurement Mode: High Speed Long Range Mode. */ + ARGUS_MODE_HIGH_SPEED_LONG_RANGE = // = 0x06 = 0b0110 + ARGUS_MODE_FLAG_LONG_RANGE | ARGUS_MODE_FLAG_HIGH_SPEED, } argus_mode_t; +/*! The data structure for the API representing a AFBR-S50 device instance. */ +typedef struct argus_hnd_t argus_hnd_t; + /*!*************************************************************************** - * @brief Generic API callback function. - * @details Invoked by the API. The content of the abstract data pointer - * depends upon the context. - * @param status The module status that caused the callback. #STATUS_OK if - * everything was as expected. - * @param data An abstract pointer to an user defined data. This will usually - * be passed to the function that also takes the callback as an - * parameter. Otherwise it has a special meaning such as - * configuration or calibration data. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @brief Measurement Ready API callback function. + * + * @details Invoked by the API whenever a measurement cycle is finished and + * new data is ready to be evaluated via the #Argus_EvaluateData API + * function. + * The callback is passed to the API via the #Argus_TriggerMeasurement + * or #Argus_StartMeasurementTimer API functions. + * The API passes the status of the currently finished measurement + * cycle to the callback as first parameters. The second parameter is + * a pointer the API handle structure. The latter is used to identify + * the calling instance of the API in case of multiple devices. + * Further it can be passed to the #Argus_EvaluateData function. + * + * @warning Since the callback is called from an interrupt context, the + * callback execution must return as fast as possible. The usual task + * in the callback is to post an event to the main thread to inform it + * about the new data and that is must call the #Argus_EvaluateData + * function. + * + * @param status The module status that caused the callback. #STATUS_OK if + * everything was as expected. + * + * @param hnd The API handle pointer to the calling instance. Identifies the + * instance of the API that was invoking the callback and thus the + * instance that must call the #Argus_EvaluateData for. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -typedef status_t (*argus_callback_t)(status_t status, void *data); +typedef status_t (*argus_measurement_ready_callback_t)(status_t status, argus_hnd_t *hnd); /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_DEF_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dfm.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dfm.h index b2517182f8..4c0182a0b3 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dfm.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dfm.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Defines the dual frequency mode (DFM) setup parameters. + * @brief This file is part of the AFBR-S50 API. + * @details Defines the dual frequency mode (DFM) setup parameters. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,24 +36,27 @@ #ifndef ARGUS_DFM_H #define ARGUS_DFM_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup argusdfm Dual Frequency Mode - * @ingroup argusapi + * @defgroup argus_dfm Dual Frequency Mode + * @ingroup argus_api * - * @brief Dual Frequency Mode (DFM) parameter definitions and API functions. + * @brief Dual Frequency Mode (DFM) parameter definitions and API functions. * - * @details The DFM is an algorithm to extend the unambiguous range of the - * sensor by utilizing two detuned measurement frequencies. + * @details The DFM is an algorithm to extend the unambiguous range of the + * sensor by utilizing two detuned measurement frequencies. * - * The AFBR-S50 API provides three measurement modes: - * - 1X: Single Frequency Measurement - * - 4X: Dual Frequency Measurement w/ 4 times the unambiguous - * range of the Single Frequency Measurement - * - 8X: Dual Frequency Measurement w/ 8 times the unambiguous - * range of the Single Frequency Measurement + * The AFBR-S50 API provides three measurement modes: + * - 1X: Single Frequency Measurement + * - 4X: Dual Frequency Measurement w/ 4 times the unambiguous + * range of the Single Frequency Measurement + * - 8X: Dual Frequency Measurement w/ 8 times the unambiguous + * range of the Single Frequency Measurement * - * @addtogroup argusdfm + * @addtogroup argus_dfm * @{ *****************************************************************************/ @@ -61,10 +64,10 @@ #define ARGUS_DFM_FRAME_COUNT (2U) /*! The Dual Frequency Mode measurement modes count. Excluding the disabled mode. */ -#define ARGUS_DFM_MODE_COUNT (2U) // expect off-mode! +#define ARGUS_DFM_MODE_COUNT (2U) // except off-mode! /*! The Dual Frequency Mode measurement modes enumeration. */ -typedef enum { +typedef enum argus_dfm_mode_t { /*! Single Frequency Measurement Mode (w/ 1x Unambiguous Range). */ DFM_MODE_OFF = 0U, @@ -78,4 +81,7 @@ typedef enum { /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_DFM_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_map.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_map.h index 64588d25f1..4ffa55656b 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_map.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_map.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Defines macros to work with pixel and ADC channel masks. + * @brief This file is part of the AFBR-S50 API. + * @details Defines macros to work with pixel and ADC channel masks. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,217 +37,409 @@ #ifndef ARGUS_MAP_H #define ARGUS_MAP_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup argusmap ADC Channel Mapping - * @ingroup argusres + * @defgroup argus_map Pixel Channel Mapping + * @ingroup argus_api * - * @brief Pixel ADC Channel Mapping + * @brief Pixel Channel Mapping * - * @details The ADC Channels of each pixel or auxiliary channel on the device - * are numbered in a way that is convenient on the chip architecture. - * The macros in this module are defined in order to map between the - * chip internal channel number (ch) to the two-dimensional - * x-y-indices or one-dimensional n-index representation. + * @details The ADC Channels of each pixel or auxiliary channel on the device + * are numbered in a way that is convenient on the chip architecture. + * The macros in this module are defined in order to map between the + * chip internal channel number (ch) to the two-dimensional + * x-y-indices or one-dimensional n-index representation. * - * @addtogroup argusmap + * @addtogroup argus_map * @{ *****************************************************************************/ -#include "api/argus_def.h" #include "utility/int_math.h" +#include +/*!*************************************************************************** + * @brief The device pixel field size in x direction (long edge). + *****************************************************************************/ +#define ARGUS_PIXELS_X 8 + +/*!*************************************************************************** + * @brief The device pixel field size in y direction (short edge). + *****************************************************************************/ +#define ARGUS_PIXELS_Y 4 + +/*!*************************************************************************** + * @brief The total device pixel count. + *****************************************************************************/ +#define ARGUS_PIXELS ((ARGUS_PIXELS_X)*(ARGUS_PIXELS_Y)) /*!***************************************************************************** - * @brief Macro to determine the pixel ADC channel number from the x-z-indices. - * @param x The x-index of the pixel. - * @param y The y-index of the pixel. - * @return The ADC channel number of the pixel. + * @brief Macro to determine the pixel ADC channel number from the x-z-indices. + * @param x The x-index of the pixel. + * @param y The y-index of the pixel. + * @return The ADC channel number of the pixel. ******************************************************************************/ #define PIXEL_XY2CH(x, y) ((((y) << 3U) & 0x10U) | (((x) ^ 0x07U) << 1U) | ((y) & 0x01U)) /*!***************************************************************************** - * @brief Macro to determine the pixel x-index from the ADC channel number. - * @param c The ADC channel number of the pixel. - * @return The x-index of the pixel. + * @brief Macro to determine the pixel x-index from the ADC channel number. + * @param c The ADC channel number of the pixel. + * @return The x-index of the pixel. ******************************************************************************/ #define PIXEL_CH2X(c) ((((c) >> 1U) ^ 0x07U) & 0x07U) /*!***************************************************************************** - * @brief Macro to determine the pixel y-index from the ADC channel number. - * @param c The ADC channel number of the pixel. - * @return The y-index of the pixel. + * @brief Macro to determine the pixel y-index from the ADC channel number. + * @param c The ADC channel number of the pixel. + * @return The y-index of the pixel. ******************************************************************************/ #define PIXEL_CH2Y(c) ((((c) >> 3U) & 0x02U) | ((c) & 0x01U)) /*!***************************************************************************** - * @brief Macro to determine the n-index from the x-y-indices. - * @param x The x-index of the pixel. - * @param y The y-index of the pixel. - * @return The n-index of the pixel. + * @brief Macro to determine the n-index from the x-y-indices. + * @param x The x-index of the pixel. + * @param y The y-index of the pixel. + * @return The n-index of the pixel. ******************************************************************************/ #define PIXEL_XY2N(x, y) (((x) << 2U) | (y)) /*!***************************************************************************** - * @brief Macro to determine the pixel x-index from the n-index. - * @param n The n-index of the pixel. - * @return The x-index number of the pixel. + * @brief Macro to determine the pixel x-index from the n-index. + * @param n The n-index of the pixel. + * @return The x-index number of the pixel. ******************************************************************************/ #define PIXEL_N2X(n) ((n) >> 2U) /*!***************************************************************************** - * @brief Macro to determine the pixel y-index from the n-index. - * @param n The n-index of the pixel. - * @return The y-index number of the pixel. + * @brief Macro to determine the pixel y-index from the n-index. + * @param n The n-index of the pixel. + * @return The y-index number of the pixel. ******************************************************************************/ #define PIXEL_N2Y(n) ((n) & 0x03U) /*!***************************************************************************** - * @brief Macro to determine the pixel n-index from the ADC channel number. - * @param n The n-index of the pixel. - * @return The ADC channel number of the pixel. + * @brief Macro to determine the pixel n-index from the ADC channel number. + * @param n The n-index of the pixel. + * @return The ADC channel number of the pixel. ******************************************************************************/ #define PIXEL_N2CH(n) ((((n) << 3U) & 0x10U) | ((((n) >> 1U) ^ 0x0EU) & 0x0EU) | ((n) & 0x01U)) /*!***************************************************************************** - * @brief Macro to determine the pixel - * @param c The ADC channel number of the pixel. - * @return The n-index of the pixel. + * @brief Macro to determine the pixel + * @param c The ADC channel number of the pixel. + * @return The n-index of the pixel. ******************************************************************************/ #define PIXEL_CH2N(c) (((((c) << 1U) ^ 0x1CU) & 0x1CU) | (((c) >> 3U) & 0x02U) | ((c) & 0x01U)) /*!***************************************************************************** - * @brief Macro to determine if a pixel given by the n-index is enabled in a pixel mask. - * @param msk 32-bit pixel mask - * @param n n-index of the pixel. - * @return True if the pixel (n) is enabled. + * @brief Macro to determine if a pixel given by the n-index is enabled in a pixel mask. + * @param msk 32-bit pixel mask + * @param n n-index of the pixel. + * @return True if the pixel (n) is enabled. ******************************************************************************/ #define PIXELN_ISENABLED(msk, n) (((msk) >> (n)) & 0x01U) /*!***************************************************************************** - * @brief Macro to enable a pixel given by the n-index in a pixel mask. - * @param msk 32-bit pixel mask - * @param n n-index of the pixel to enable. + * @brief Macro to enable a pixel given by the n-index in a pixel mask. + * @param msk 32-bit pixel mask + * @param n n-index of the pixel to enable. ******************************************************************************/ #define PIXELN_ENABLE(msk, n) ((msk) |= (0x01U << (n))) /*!***************************************************************************** - * @brief Macro disable a pixel given by the n-index in a pixel mask. - * @param msk 32-bit pixel mask - * @param n n-index of the pixel to disable. + * @brief Macro disable a pixel given by the n-index in a pixel mask. + * @param msk 32-bit pixel mask + * @param n n-index of the pixel to disable. ******************************************************************************/ #define PIXELN_DISABLE(msk, n) ((msk) &= (~(0x01U << (n)))) /*!***************************************************************************** - * @brief Macro to determine if an ADC pixel channel is enabled from a pixel mask. - * @param msk The 32-bit pixel mask - * @param c The ADC channel number of the pixel. - * @return True if the specified pixel ADC channel is enabled. + * @brief Macro to determine if an ADC pixel channel is enabled from a pixel mask. + * @param msk The 32-bit pixel mask + * @param c The ADC channel number of the pixel. + * @return True if the specified pixel ADC channel is enabled. ******************************************************************************/ #define PIXELCH_ISENABLED(msk, c) (PIXELN_ISENABLED(msk, PIXEL_CH2N(c))) /*!***************************************************************************** - * @brief Macro to enable an ADC pixel channel in a pixel mask. - * @param msk The 32-bit pixel mask - * @param c The pixel ADC channel number to enable. + * @brief Macro to enable an ADC pixel channel in a pixel mask. + * @param msk The 32-bit pixel mask + * @param c The pixel ADC channel number to enable. ******************************************************************************/ #define PIXELCH_ENABLE(msk, c) (PIXELN_ENABLE(msk, PIXEL_CH2N(c))) /*!***************************************************************************** - * @brief Macro to disable an ADC pixel channel in a pixel mask. - * @param msk The 32-bit pixel mask - * @param c The pixel ADC channel number to disable. + * @brief Macro to disable an ADC pixel channel in a pixel mask. + * @param msk The 32-bit pixel mask + * @param c The pixel ADC channel number to disable. ******************************************************************************/ #define PIXELCH_DISABLE(msk, c) (PIXELN_DISABLE(msk, PIXEL_CH2N(c))) /*!***************************************************************************** - * @brief Macro to determine if a pixel given by the x-y-indices is enabled in a pixel mask. - * @param msk 32-bit pixel mask - * @param x x-index of the pixel. - * @param y y-index of the pixel. - * @return True if the pixel (x,y) is enabled. + * @brief Macro to determine if a pixel given by the x-y-indices is enabled in a pixel mask. + * @param msk 32-bit pixel mask + * @param x x-index of the pixel. + * @param y y-index of the pixel. + * @return True if the pixel (x,y) is enabled. ******************************************************************************/ #define PIXELXY_ISENABLED(msk, x, y) (PIXELN_ISENABLED(msk, PIXEL_XY2N(x, y))) /*!***************************************************************************** - * @brief Macro to enable a pixel given by the x-y-indices in a pixel mask. - * @param msk 32-bit pixel mask - * @param x x-index of the pixel to enable. - * @param y y-index of the pixel to enable. + * @brief Macro to enable a pixel given by the x-y-indices in a pixel mask. + * @param msk 32-bit pixel mask + * @param x x-index of the pixel to enable. + * @param y y-index of the pixel to enable. ******************************************************************************/ #define PIXELXY_ENABLE(msk, x, y) (PIXELN_ENABLE(msk, PIXEL_XY2N(x, y))) /*!***************************************************************************** - * @brief Macro disable a pixel given by the x-y-indices in a pixel mask. - * @param msk 32-bit pixel mask - * @param x x-index of the pixel to disable. - * @param y y-index of the pixel to disable. + * @brief Macro disable a pixel given by the x-y-indices in a pixel mask. + * @param msk 32-bit pixel mask + * @param x x-index of the pixel to disable. + * @param y y-index of the pixel to disable. ******************************************************************************/ #define PIXELXY_DISABLE(msk, x, y) (PIXELN_DISABLE(msk, PIXEL_XY2N(x, y))) /*!***************************************************************************** - * @brief Macro to determine if an ADC channel is enabled in a channel mask. - * @param msk 32-bit channel mask - * @param ch channel number of the ADC channel. - * @return True if the ADC channel is enabled. + * @brief Macro to determine if an ADC channel is enabled in a channel mask. + * @param msk 32-bit channel mask + * @param ch channel number of the ADC channel. + * @return True if the ADC channel is enabled. ******************************************************************************/ #define CHANNELN_ISENABLED(msk, ch) (((msk) >> ((ch) - 32U)) & 0x01U) /*!***************************************************************************** - * @brief Macro to determine if an ADC channel is enabled in a channel mask. - * @param msk 32-bit channel mask - * @param ch channel number of the ADC channel to enabled. + * @brief Macro to determine if an ADC channel is enabled in a channel mask. + * @param msk 32-bit channel mask + * @param ch channel number of the ADC channel to enabled. ******************************************************************************/ #define CHANNELN_ENABLE(msk, ch) ((msk) |= (0x01U << ((ch) - 32U))) /*!***************************************************************************** - * @brief Macro to determine if an ADC channel is disabled in a channel mask. - * @param msk 32-bit channel mask - * @param ch channel number of the ADC channel to disable. + * @brief Macro to determine if an ADC channel is disabled in a channel mask. + * @param msk 32-bit channel mask + * @param ch channel number of the ADC channel to disable. ******************************************************************************/ #define CHANNELN_DISABLE(msk, ch) ((msk) &= (~(0x01U << ((ch) - 32U)))) /*!***************************************************************************** - * @brief Macro to determine the number of enabled pixel/channels in a mask - * via a popcount algorithm. - * @param pxmsk 32-bit pixel mask - * @return The count of enabled pixel channels. + * @brief Macro to determine the number of enabled pixel/channels in a mask + * via a popcount algorithm. + * @param pxmsk 32-bit pixel mask + * @return The count of enabled pixel channels. ******************************************************************************/ #define PIXEL_COUNT(pxmsk) popcount(pxmsk) /*!***************************************************************************** - * @brief Macro to determine the number of enabled channels via a popcount - * algorithm. - * @param pxmsk 32-bit pixel mask - * @param chmsk 32-bit channel mask - * @return The count of enabled ADC channels. + * @brief Macro to determine the number of enabled channels via a popcount + * algorithm. + * @param pxmsk 32-bit pixel mask + * @param chmsk 32-bit channel mask + * @return The count of enabled ADC channels. ******************************************************************************/ #define CHANNEL_COUNT(pxmsk, chmsk) (popcount(pxmsk) + popcount(chmsk)) /*!***************************************************************************** - * @brief Converts a raw ADC channel mask to a x-y-sorted pixel mask. - * @param msk The raw ADC channel mask to be converted. - * @return The converted x-y-sorted pixel mask. + * @brief Converts a raw ADC channel mask to a x-y-sorted pixel mask. + * @param msk The raw ADC channel mask to be converted. + * @return The converted x-y-sorted pixel mask. ******************************************************************************/ static inline uint32_t ChannelToPixelMask(uint32_t msk) { uint32_t res = 0; for (uint_fast8_t n = 0; n < 32; n += 2) { - res |= ((msk >> PIXEL_N2CH(n)) & 0x3U) << n; + res |= ((msk >> PIXEL_N2CH(n)) & 0x3U) << n; // sets 2 bits at once } return res; } +/*!***************************************************************************** + * @brief Converts a x-y-sorted pixel mask to a raw ADC channel mask. + * @param msk The x-y-sorted pixel channel mask to be converted. + * @return The converted raw ADC channel mask. + ******************************************************************************/ +static inline uint32_t PixelToChannelMask(uint32_t msk) +{ + uint32_t res = 0; + + for (uint_fast8_t ch = 0; ch < 32; ch += 2) { + res |= ((msk >> PIXEL_CH2N(ch)) & 0x3U) << ch; // sets 2 bits at once + } + + return res; +} + + +/*!***************************************************************************** + * @brief Shifts a pixel mask by a given offset. + * + * @details This moves the selected pixel pattern by a specified number of + * pixels in x and y direction. + * If the shift in y direction is odd (e.g +1), the pattern will be + * shifted by +0.5 or -0.5 in x direction due to the hexagonal shape + * of the pixel field. Thus, a center pixel (usually the Golden Pixel) + * is determined that is used to determine if the pattern is shifted + * by +0.5 or -0.5 pixels in x direction. The center pixel is then + * always shifted without changing the x index and the surrounding + * pixels are adopting its x index accordingly. + * + * Example: Consider the flower pattern, i.e. the Golden Pixel (e.g. + * 5/2) is selected and all is direct neighbors (i.e. 5/1, 6/1, 6/2, + * 6/3, 5/3, 4/2). If the pattern is shifted by -1 in y direction, the + * new Golden Pixel would be 5/1. Now all surrounding pixels are + * selected, namely 4/0, 4/1, 4/2, 5/0, 5/2, 6/1). This yields again + * the flower around the Golden Pixel. + * + * Thus, the pixels can not all be shifted by the same dx/dy values due + * to the hexagonal shape of the pixel field, e.g. the upper right + * neighbor of 5/2 is 5/1 but the upper right neighbor of 5/1 is NOT + * 5/0 but 4/0! + * This happens only if the shift in y direction is an odd number. + * The algorithm to determine new indices is as follows: + * - If the shift in y direction is even (e.g. +2, -2), no compensation + * of the hexagonal shape is needed; skip compensation, simply + * add/subtract indices. + * - If the center pixel y index is even, pixels that will have even y + * index after the shift will be additionally shifted by -1 in x + * direction. + * - If the center pixel y index is odd, pixel that will have odd y + * index after the shift will be additionally shifted by +1 in x + * direction. + * + * @see Please also refer to the function #Argus_GetCalibrationGoldenPixel + * to obtain the current Golden Pixel location. + * + * @param pixel_mask The x-y-sorted pixel mask to be shifted. + * @param dx The number of pixel to shift in x direction. + * @param dy The number of pixel to shift in y direction. + * @param center_y The center y index of the pattern that is shifted. + * @return The shifted pixel mask. + ******************************************************************************/ +static inline uint32_t ShiftSelectedPixels(const uint32_t pixel_mask, + const int8_t dx, + const int8_t dy, + const uint8_t center_y) +{ + if (dx == 0 && dy == 0) { return pixel_mask; } + + uint32_t shifted_mask = 0; + + for (uint8_t x = 0; x < ARGUS_PIXELS_X; ++x) { + for (uint8_t y = 0; y < ARGUS_PIXELS_Y; ++y) { + int8_t x_src = x - dx; + int8_t y_src = y - dy; + + if (dy & 0x1) { + /* Compensate for hexagonal pixel shape. */ + if ((center_y & 0x1) && (y & 0x1)) { + x_src--; + } + + if (!(center_y & 0x1) && !(y & 0x1)) { + x_src++; + } + } + + if (x_src < 0 || x_src >= ARGUS_PIXELS_X) { continue; } + + if (y_src < 0 || y_src >= ARGUS_PIXELS_Y) { continue; } + + if (PIXELXY_ISENABLED(pixel_mask, x_src, y_src)) { + PIXELXY_ENABLE(shifted_mask, x, y); + } + } + } + + return shifted_mask; +} + +/*!***************************************************************************** + * @brief Fills a pixel mask to a specified number of pixels around a center pixel. + * + * @details The pixel mask is iteratively filled with the nearest pixel to a + * specified center pixel until a specified number of pixels is achieved. + * The distance between two pixel is determined via a quadratic metric, + * i.e. dx^2 + dy^2. Pixels towards the lower x indices are preferred. + * + * Note that the distance of only calculated approximately, e.g. the + * y distance of pixels is considered to be 2 instead of cos(60)*2. + * + * Nothing is done if the number of pixels already exceeds the specified + * /p pixel_count parameter. + * + * @see Please also refer to the function #Argus_GetCalibrationGoldenPixel + * to obtain the current Golden Pixel location. + * + * @param pixel_mask The x-y-sorted pixel mask to be filled with pixels. + * @param pixel_count The final number of pixels in the pixel mask. + * @param center_x The center pixel x-index. + * @param center_y The center pixel y-index. + * @return The filled pixel mask with at least /p pixel_count pixels selected. + ******************************************************************************/ +static inline uint32_t FillPixelMask(uint32_t pixel_mask, + const uint8_t pixel_count, + const uint8_t center_x, + const uint8_t center_y) +{ + assert(pixel_count <= ARGUS_PIXELS); + assert(center_x < ARGUS_PIXELS_X); + assert(center_y < ARGUS_PIXELS_Y); + + if (pixel_count == ARGUS_PIXELS) { return 0xFFFFFFFFU; } + + /* If the pattern was shifted towards boundaries, the pixel count may have + * decreased. In this case, the pixels closest to the reference pixel are + * selected. Pixel towards lower x index are prioritized. */ + while (pixel_count > PIXEL_COUNT(pixel_mask)) { + int32_t min_dist = INT32_MAX; + int8_t min_x = -1; + int8_t min_y = -1; + + /* Find nearest not selected pixel. */ + for (uint8_t x = 0; x < ARGUS_PIXELS_X; ++x) { + for (uint8_t y = 0; y < ARGUS_PIXELS_Y; ++y) { + if (!PIXELXY_ISENABLED(pixel_mask, x, y)) { + int32_t distx = (x - center_x) << 1; + + if (!(y & 0x1)) { distx++; } + + if (!(center_y & 0x1)) { distx--; } + + const int32_t disty = (y - center_y) << 1; + int32_t dist = distx * distx + disty * disty; + + if (dist < min_dist) { + min_dist = dist; + min_x = x; + min_y = y; + } + } + } + } + + assert(min_x >= 0 && min_x < ARGUS_PIXELS_X); + assert(min_y >= 0 && min_y < ARGUS_PIXELS_Y); + assert(!PIXELXY_ISENABLED(pixel_mask, min_x, min_y)); + PIXELXY_ENABLE(pixel_mask, min_x, min_y); + } + + return pixel_mask; +} /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_MAP_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_meas.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_meas.h index 0e074c6a8b..7a0fa31aa5 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_meas.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_meas.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 hardware API. - * @details Defines the generic measurement parameters and data structures. + * @brief This file is part of the AFBR-S50 hardware API. + * @details Defines the generic measurement parameters and data structures. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,17 +36,20 @@ #ifndef ARGUS_MEAS_H #define ARGUS_MEAS_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup argusmeas Measurement/Device Control - * @ingroup argusapi + * @defgroup argus_meas Measurement/Device Control + * @ingroup argus_api * - * @brief Measurement/Device control module + * @brief Measurement/Device control module * - * @details This module contains measurement and device control specific - * definitions and methods. + * @details This module contains measurement and device control specific + * definitions and methods. * - * @addtogroup argusmeas + * @addtogroup argus_meas * @{ *****************************************************************************/ @@ -66,11 +69,11 @@ #define ARGUS_AUX_DATA_SIZE (3U * ARGUS_AUX_CHANNEL_COUNT) // 3 bytes * x channels * 1 phase /*!*************************************************************************** - * @brief The device measurement configuration structure. - * @details The portion of the configuration data that belongs to the - * measurement cycle. I.e. the data that defines a measurement frame. + * @brief The device measurement configuration structure. + * @details The portion of the configuration data that belongs to the + * measurement cycle. I.e. the data that defines a measurement frame. *****************************************************************************/ -typedef struct { +typedef struct argus_meas_frame_t { /*! Frame integration time in microseconds. * The integration time determines the measured time between * the start signal and the IRQ. Note that this value will be @@ -82,13 +85,13 @@ typedef struct { /*! Pixel enabled mask for the 32 pixels sorted * by x-y-indices. - * See [pixel mapping](@ref argusmap) for more + * See [pixel mapping](@ref argus_map) for more * details on the pixel mask. */ uint32_t PxEnMask; /*! ADS channel enabled mask for the remaining * channels 31 .. 63 (miscellaneous values). - * See [pixel mapping](@ref argusmap) for more + * See [pixel mapping](@ref argus_map) for more * details on the ADC channel mask. */ uint32_t ChEnMask; @@ -113,9 +116,6 @@ typedef struct { * Determines the optical output power. */ uq12_4_t OutputPower; - /*! The amplitude that is evaluated and used in the DCA module. */ - uq12_4_t DCAAmplitude; - /*! Laser Bias Current Settings in LSB. */ uint8_t BiasCurrent; @@ -133,4 +133,7 @@ typedef struct { } argus_meas_frame_t; /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_MEAS_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_offset.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_offset.h new file mode 100644 index 0000000000..7a41440f39 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_offset.h @@ -0,0 +1,59 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 hardware API. + * @details Defines the generic device calibration API. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef ARGUS_OFFSET_H +#define ARGUS_OFFSET_H + +/*!*************************************************************************** + * @addtogroup argus_cal + * @{ + *****************************************************************************/ + +#include "argus_def.h" + +/*!*************************************************************************** + * @brief Pixel Range Offset Table. + * @details Contains pixel range offset values for all 32 active pixels. + *****************************************************************************/ +typedef struct argus_cal_offset_table_t { + /*! The offset values per pixel in meter and Q0.15 format. */ + q0_15_t Table[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]; + +} argus_cal_offset_table_t; + + +/*! @} */ +#endif /* ARGUS_OFFSET_T */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_pba.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_pba.h index 07b4853bda..f28576500d 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_pba.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_pba.h @@ -1,12 +1,12 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Defines the pixel binning algorithm (PBA) setup parameters and - * data structure. + * @brief This file is part of the AFBR-S50 API. + * @details Defines the pixel binning algorithm (PBA) setup parameters and + * data structure. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,86 +37,98 @@ #ifndef ARGUS_PBA_H #define ARGUS_PBA_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup arguspba Pixel Binning Algorithm - * @ingroup argusapi + * @defgroup argus_pba Pixel Binning Algorithm + * @ingroup argus_api * - * @brief Pixel Binning Algorithm (PBA) parameter definitions and API functions. + * @brief Pixel Binning Algorithm (PBA) parameter definitions and API functions. * - * @details Defines the generic pixel binning algorithm (PBA) setup parameters - * and data structure. + * @details Defines the generic Pixel Binning Algorithm (PBA) setup parameters + * and data structure. * - * The PBA module contains filter algorithms that determine the - * pixels with the best signal quality and extract an 1d distance - * information from the filtered pixels. + * The PBA module contains filter algorithms that determine the + * pixels with the best signal quality and extract an 1D distance + * information from the filtered pixels by averaging them in a + * specified way. * - * The pixel filter algorithm is a three-stage filter with a - * fallback value: + * The Pixel Binning Algorithm is a three-stage filter with a + * fallback value: * - * -# A fixed pre-filter mask is applied to statically disable - * specified pixels. - * -# A relative and absolute amplitude filter is applied in the - * second stage. The relative filter is determined by a ratio - * of the maximum amplitude off all available (i.e. not filtered - * in stage 1) pixels. Pixels that have an amplitude below the - * relative threshold are dismissed. The same holds true for - * the absolute amplitude threshold. All pixel with smaller - * amplitude are dismissed.\n - * The relative threshold is useful to setup a distance - * measurement scenario. All well illuminated pixels are - * selected and considered for the final 1d distance. The - * absolute threshold is used to dismiss pixels that are below - * the noise level. The latter would be considered for the 1d - * result if the maximum amplitude is already very low. - * -# A distance filter is used to distinguish pixels that target - * the actual object from pixels that see the brighter background, - * e.g. white walls. Thus, the pixel with the minimum distance - * is referenced and all pixel that have a distance between - * the minimum and the given minimum distance scope are selected - * for the 1d distance result. The minimum distance scope is - * determined by an relative (to the current minimum distance) - * and an absolute value. The larger scope value is the - * relevant one, i.e. the relative distance scope can be used - * to heed the increasing noise at larger distances. - * -# If all of the above filters fail to determine a single valid - * pixel, the golden pixel is used as a fallback value. The - * golden pixel is the pixel that sits right at the focus point - * of the optics at large distances. - * . + * -# A fixed pre-filter mask is applied to statically disable + * specified pixels. + * -# A relative and absolute amplitude filter is applied in the + * second stage. The relative filter is determined by a ratio + * of the maximum amplitude off all available (i.e. not filtered + * in stage 1) pixels. Pixels that have an amplitude below the + * relative threshold are dismissed. The same holds true for + * the absolute amplitude threshold. All pixel with smaller + * amplitude are dismissed.\n + * Note that the absolute amplitude threshold is disabled if + * the Golden Pixel (see below) is also disabled in order to + * prevent invalid filtering for multi-pixel devices.\n + * The relative threshold is useful to setup a distance + * measurement scenario. All well illuminated pixels are + * selected and considered for the final 1D distance. The + * absolute threshold is used to dismiss pixels that are below + * the noise level. The latter would be considered for the 1D + * result if the maximum amplitude is already very low. + * -# An absolute minimum distance filter is applied in addition + * to the amplitude filter. This removes all pixel that have + * a lower distance than the specified threshold. This is used + * to remove invalid pixels that can be detected by a physically + * not correct negative distance. + * -# A distance filter is used to distinguish pixels that target + * the actual object from pixels that see the brighter background, + * e.g. white walls. Thus, the pixel with the minimum distance + * is referenced and all pixel that have a distance between + * the minimum and the given minimum distance scope are selected + * for the 1D distance result. The minimum distance scope is + * determined by an relative (to the current minimum distance) + * and an absolute value. The larger scope value is the + * relevant one, i.e. the relative distance scope can be used + * to heed the increasing noise at larger distances. + * -# If all of the above filters fail to determine a single valid + * pixel, the Golden Pixel is used as a fallback value. The + * Golden Pixel is the pixel that sits right at the focus point + * of the optics at large distances. + * . * - * After filtering is done, there may be more than a single pixel - * left to determine the 1d signal. Therefore several averaging - * methods are implemented to obtain the best 1d result from many - * pixels. See #argus_pba_averaging_mode_t for details. + * After filtering is done, there may be more than a single pixel + * left to determine the 1D signal. Therefore several averaging + * methods are implemented to obtain the best 1D result from many + * pixels. See #argus_pba_averaging_mode_t for details. * * - * @addtogroup arguspba + * @addtogroup argus_pba * @{ *****************************************************************************/ #include "argus_def.h" /*!*************************************************************************** - * @brief Enable flags for the pixel binning algorithm. + * @brief Enable flags for the pixel binning algorithm. * * @details Determines the pixel binning algorithm feature enable status. - * - [0]: #PBA_ENABLE: Enables the pixel binning feature. - * - [1]: reserved - * - [2]: reserved - * - [3]: reserved - * - [4]: reserved - * - [5]: #PBA_ENABLE_GOLDPX: Enables the golden pixel feature. - * - [6]: #PBA_ENABLE_MIN_DIST_SCOPE: Enables the minimum distance scope - * feature. - * - [7]: reserved - * . + * - [0]: #PBA_ENABLE: Enables the pixel binning feature. + * - [1]: reserved + * - [2]: reserved + * - [3]: reserved + * - [4]: reserved + * - [5]: #PBA_ENABLE_GOLDPX: Enables the Golden Pixel feature. + * - [6]: #PBA_ENABLE_MIN_DIST_SCOPE: Enables the minimum distance scope + * feature. + * - [7]: reserved + * . *****************************************************************************/ -typedef enum { +typedef enum argus_pba_flags_t { /*! Enables the pixel binning feature. */ PBA_ENABLE = 1U << 0U, - /*! Enables the golden pixel. */ + /*! Enables the Golden Pixel. */ PBA_ENABLE_GOLDPX = 1U << 5U, /*! Enables the minimum distance scope filter. */ @@ -125,9 +137,9 @@ typedef enum { } argus_pba_flags_t; /*!*************************************************************************** - * @brief The averaging modes for the pixel binning algorithm. + * @brief The averaging modes for the pixel binning algorithm. *****************************************************************************/ -typedef enum { +typedef enum argus_pba_averaging_mode_t { /*! Evaluate the 1D range from all available pixels using * a simple average. */ PBA_SIMPLE_AVG = 1U, @@ -140,11 +152,12 @@ typedef enum { } argus_pba_averaging_mode_t; /*!*************************************************************************** - * @brief The pixel binning algorithm settings data structure. - * @details Describes the pixel binning algorithm settings. + * @brief The pixel binning algorithm settings data structure. + * @details Describes the pixel binning algorithm settings. *****************************************************************************/ typedef struct { - /*! Enables the pixel binning features. + /*! Enables the Pixel Binning Algorithm. + * * Each bit may enable a different feature. See #argus_pba_flags_t * for details about the enabled flags. */ argus_pba_flags_t Enabled; @@ -156,6 +169,7 @@ typedef struct { argus_pba_averaging_mode_t AveragingMode; /*! The Relative amplitude threshold value (in %) of the max. amplitude. + * * Pixels with amplitude below this threshold value are dismissed. * * All available values from the 8-bit representation are valid. @@ -165,22 +179,27 @@ typedef struct { uq0_8_t RelAmplThreshold; /*! The relative minimum distance scope value in %. - * Pixels that have a range value within [x0, x0 + dx] are considered - * for the pixel binning, where x0 is the minimum distance of all - * amplitude picked pixels and dx is the minimum distance scope value. - * The minimum distance scope value will be the maximum of relative - * and absolute value. + * + * Pixels that have a range value within [x0, x0 + dx] are considered + * for the pixel binning, where x0 is the minimum distance of all + * amplitude picked pixels and dx is the minimum distance scope value. + * The minimum distance scope value will be the maximum of relative + * and absolute value. * * All available values from the 8-bit representation are valid. * The actual percentage value is determined by 100%/256*x. * - * Special values: + * Special values: * - 0: Use 0 for absolute value only or to choose the pixel with the - * minimum distance only (of also the absolute value is 0)! */ + * minimum distance only (of also the absolute value is 0)! */ uq0_8_t RelMinDistanceScope; - /*! The Absolute amplitude threshold value in LSB. - * Pixels with amplitude below this threshold value are dismissed. + /*! The absolute amplitude threshold value in LSB. + * + * Pixels with amplitude below this threshold value are dismissed. + * + * The absolute amplitude threshold is only valid if the Golden Pixel + * mode is enabled. Otherwise, the threshold is set to 0 LSB internally. * * All available values from the 16-bit representation are valid. * The actual LSB value is determined by x/16. @@ -189,33 +208,42 @@ typedef struct { uq12_4_t AbsAmplThreshold; /*! The absolute minimum distance scope value in m. - * Pixels that have a range value within [x0, x0 + dx] are considered - * for the pixel binning, where x0 is the minimum distance of all - * amplitude picked pixels and dx is the minimum distance scope value. - * The minimum distance scope value will be the maximum of relative - * and absolute value. + * + * Pixels that have a range value within [x0, x0 + dx] are considered + * for the pixel binning, where x0 is the minimum distance of all + * amplitude picked pixels and dx is the minimum distance scope value. + * The minimum distance scope value will be the maximum of relative + * and absolute value. * * All available values from the 16-bit representation are valid. * The actual LSB value is determined by x/2^15. * - * Special values: + * Special values: * - 0: Use 0 for relative value only or to choose the pixel with the - * minimum distance only (of also the relative value is 0)! */ + * minimum distance only (of also the relative value is 0)! */ uq1_15_t AbsMinDistanceScope; - /*! The pre-filter pixel mask determines the pixel channels that are - * statically excluded from the pixel binning (i.e. 1D distance) result. + /*! The absolute minimum distance threshold value in m. * - * The pixel enabled mask is an 32-bit mask that determines the - * device internal channel number. It is recommended to use the - * - #PIXELXY_ISENABLED(msk, x, y) - * - #PIXELXY_ENABLE(msk, x, y) - * - #PIXELXY_DISABLE(msk, x, y) - * . - * macros to work with the pixel enable masks. */ + * Pixels with distance below this threshold value are dismissed. */ + q9_22_t AbsMinDistanceThreshold; + + /*! The pre-filter pixel mask determines the pixel channels that are + * statically excluded from the pixel binning (i.e. 1D distance) result. + * + * The pixel enabled mask is an 32-bit mask that determines the + * device internal channel number. It is recommended to use the + * - #PIXELXY_ISENABLED(msk, x, y) + * - #PIXELXY_ENABLE(msk, x, y) + * - #PIXELXY_DISABLE(msk, x, y) + * . + * macros to work with the pixel enable masks. */ uint32_t PrefilterMask; } argus_cfg_pba_t; /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_PBA_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_px.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_px.h index faa031aeb7..a739cea7f3 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_px.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_px.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Defines the device pixel measurement results data structure. + * @brief This file is part of the AFBR-S50 API. + * @details Defines the device pixel measurement results data structure. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,34 +36,40 @@ #ifndef ARGUS_PX_H #define ARGUS_PX_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @addtogroup argusres + * @addtogroup argus_res * @{ *****************************************************************************/ +#include "argus_def.h" + + /*! Maximum amplitude value in UQ12.4 format. */ -#define ARGUS_AMPLITUDE_MAX (0xFFF0U) +#define ARGUS_AMPLITUDE_MAX (0xFFF0U) /*! Maximum range value in Q9.22 format. * Also used as a special value to determine no object detected or infinity range. */ #define ARGUS_RANGE_MAX (Q9_22_MAX) /*!*************************************************************************** - * @brief Status flags for the evaluated pixel structure. + * @brief Status flags for the evaluated pixel structure. * * @details Determines the pixel status. 0 means OK (#PIXEL_OK). - * - [0]: #PIXEL_OFF: Pixel was disabled and not read from the device. - * - [1]: #PIXEL_SAT: The pixel was saturated. - * - [2]: #PIXEL_BIN_EXCL: The pixel was excluded from the 1D result. - * - [3]: #PIXEL_AMPL_MIN: The pixel amplitude has evaluated to 0. - * - [4]: #PIXEL_PREFILTERED: The was pre-filtered by static mask. - * - [5]: #PIXEL_NO_SIGNAL: The pixel has no valid signal. - * - [6]: #PIXEL_OUT_OF_SYNC: The pixel has lost signal trace. - * - [7]: #PIXEL_STALLED: The pixel value is stalled due to errors. - * . + * - [0]: #PIXEL_OFF: Pixel was disabled and not read from the device. + * - [1]: #PIXEL_SAT: The pixel was saturated. + * - [2]: #PIXEL_BIN_EXCL: The pixel was excluded from the 1D result. + * - [3]: #PIXEL_INVALID: The pixel data is invalid. + * - [4]: #PIXEL_PREFILTERED: The was pre-filtered by static mask. + * - [5]: #PIXEL_NO_SIGNAL: The pixel has no valid signal. + * - [6]: #PIXEL_OUT_OF_SYNC: The pixel has lost signal trace. + * - [7]: #PIXEL_STALLED: The pixel value is stalled due to errors. + * . *****************************************************************************/ -typedef enum { +typedef enum argus_px_status_t { /*! 0x00: Pixel status OK. */ PIXEL_OK = 0, @@ -77,43 +83,45 @@ typedef enum { /*! 0x04: Pixel is excluded from the pixel binning (1d) result. */ PIXEL_BIN_EXCL = 1U << 2U, - /*! 0x08: Pixel amplitude minimum underrun - * (i.e. the amplitude calculation yields 0). */ - PIXEL_AMPL_MIN = 1U << 3U, + /*! 0x08: Pixel has invalid data due to miscellaneous reasons, e.g. + * - Amplitude calculates to 0 (i.e. division by 0) + * - Golden Pixel is invalid due to other saturated pixel. + * - Range/distance is negative. */ + PIXEL_INVALID = 1U << 3U, /*! 0x10: Pixel is pre-filtered by the static pixel binning pre-filter mask, - * i.e. the pixel is disabled by software. */ + * i.e. the pixel is disabled by software. */ PIXEL_PREFILTERED = 1U << 4U, /*! 0x20: Pixel amplitude is below its threshold value. The received signal - * strength is too low to evaluate a valid signal. The range value is - * set to the maximum possible value (approx. 512 m). */ + * strength is too low to evaluate a valid signal. The range value is + * set to the maximum possible value (approx. 512 m). */ PIXEL_NO_SIGNAL = 1U << 5U, /*! 0x40: Pixel is not in sync with respect to the dual frequency algorithm. - * I.e. the pixel may have a correct value but is estimated into the - * wrong unambiguous window. */ + * I.e. the pixel may have a correct value but is estimated into the + * wrong unambiguous window. */ PIXEL_OUT_OF_SYNC = 1U << 6U, /*! 0x80: Pixel is stalled due to one of the following reasons: - * - #PIXEL_SAT - * - #PIXEL_AMPL_MIN - * - #PIXEL_OUT_OF_SYNC - * - Global Measurement Error - * . - * A stalled pixel does not update its measurement data and keeps the - * previous values. If the issue is resolved, the stall disappears and - * the pixel is updating again. */ + * - #PIXEL_SAT + * - #PIXEL_INVALID + * - #PIXEL_OUT_OF_SYNC + * - Global Measurement Error + * . + * A stalled pixel does not update its measurement data and keeps the + * previous values. If the issue is resolved, the stall disappears and + * the pixel is updating again. */ PIXEL_STALLED = 1U << 7U } argus_px_status_t; /*!*************************************************************************** - * @brief The evaluated measurement results per pixel. - * @details This structure contains the evaluated data for a single pixel.\n - * If the amplitude is 0, the pixel is turned off or has invalid data. + * @brief The evaluated measurement results per pixel. + * @details This structure contains the evaluated data for a single pixel.\n + * If the amplitude is 0, the pixel is turned off or has invalid data. *****************************************************************************/ -typedef struct { +typedef struct argus_pixel_t { /*! Range Values from the device in meter. It is the actual distance before * software adjustments/calibrations. */ q9_22_t Range; @@ -141,14 +149,23 @@ typedef struct { /*!*************************************************************************** * @brief Representation of a correlation vector containing sine/cosine components. *****************************************************************************/ -typedef struct { - /*! The sine component. */ - q15_16_t S; +typedef struct argus_vector_t { + union { + /*! The sine [0] and cosine [1] components. */ + q15_16_t SC[2]; - /*! The cosine component. */ - q15_16_t C; + struct { + /*! The sine component. */ + q15_16_t S; + /*! The cosine component. */ + q15_16_t C; + }; + }; } argus_vector_t; /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_PX_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_res.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_res.h index f59d817634..7cb81bfcf1 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_res.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_res.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Defines the generic measurement results data structure. + * @brief This file is part of the AFBR-S50 API. + * @details Defines the generic measurement results data structure. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,35 +36,39 @@ #ifndef ARGUS_RES_H #define ARGUS_RES_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup argusres Measurement Data - * @ingroup argusapi + * @defgroup argus_res Measurement Data + * @ingroup argus_api * - * @brief Measurement results data structures. + * @brief Measurement results data structures. * - * @details The interface defines all data structures that correspond to - * the AFBR-S50 measurement results, e.g. - * - 1D distance and amplitude values, - * - 3D distance and amplitude values (i.e. per pixel), - * - Auxiliary channel measurement results (VDD, IAPD, temperature, ...) - * - Device and result status - * - ... - * . + * @details The interface defines all data structures that correspond to + * the AFBR-S50 measurement results, e.g. + * - 1D distance and amplitude values, + * - 3D distance and amplitude values (i.e. per pixel), + * - Auxiliary channel measurement results (VDD, IAPD, temperature, ...) + * - Device and result status + * - ... + * . * - * @addtogroup argusres + * @addtogroup argus_res * @{ *****************************************************************************/ -#include "argus_def.h" #include "argus_px.h" +#include "argus_def.h" #include "argus_meas.h" +#include "argus_xtalk.h" /*!*************************************************************************** - * @brief The 1d measurement results data structure. + * @brief The 1d measurement results data structure. * @details The 1d measurement results obtained by the Pixel Binning Algorithm. *****************************************************************************/ -typedef struct { +typedef struct argus_results_bin_t { /*! Raw 1D range value in meter (Q9.22 format). The distance obtained by * the Pixel Binning Algorithm from the current measurement frame. */ q9_22_t Range; @@ -83,11 +87,11 @@ typedef struct { } argus_results_bin_t; /*!*************************************************************************** - * @brief The auxiliary measurement results data structure. - * @details The auxiliary measurement results obtained by the auxiliary task.\n - * Special values, i.e. 0xFFFFU, indicate no readout value available. + * @brief The auxiliary measurement results data structure. + * @details The auxiliary measurement results obtained by the auxiliary task.\n + * Special values, i.e. 0xFFFFU, indicate no readout value available. *****************************************************************************/ -typedef struct { +typedef struct argus_results_aux_t { /*! VDD ADC channel readout value.\n * Special Value if no value has been measured:\n * Invalid/NotAvailable = 0xFFFFU (UQ12_4_MAX) */ @@ -129,32 +133,66 @@ typedef struct { } argus_results_aux_t; /*!*************************************************************************** - * @brief The measurement results data structure. + * @brief The debug data of measurement results data structure. + * @details This data structure will be filled with API internal data for + * debugging purposes. + *****************************************************************************/ +typedef struct argus_results_debug_t { + /*! The amplitude that is evaluated and used in the DCA module. */ + uq12_4_t DCAAmplitude; + + /*! Raw x-y-sorted ADC results from the device.\n + * Data is arranged as 32-bit values in following order: + * index > phase; where index is pixel number n and auxiliary ADC channel.\n + * Note that disabled pixels are skipped.\n + * e.g. [n=0,p=0][n=0,p=1]..[n=0,p=3][n=1,p=0]...[n=1,p=3]...[n=31,p=3] */ + uint32_t Data[ARGUS_RAW_DATA_VALUES]; + + /*! The current crosstalk correction values as determined by the + * crosstalk predictor algorithm. This is basically the temperature + * dependent portion of the crosstalk correction.\n + * Note that there are two values for the upper and lower two rows + * respectively. */ + xtalk_t XtalkPredictor[ARGUS_PIXELS_Y / 2U]; + + /*! The current crosstalk correction values as determined by the + * crosstalk monitor algorithm. This is a dynamic portion of the + * crosstalk correction that is determined by monitoring passive + * pixels.\n + * Note that the values are valid row-wise. */ + xtalk_t XtalkMonitor[ARGUS_PIXELS_Y]; + +} argus_results_debug_t; + +/*!*************************************************************************** + * @brief The measurement results data structure. * @details This structure contains all information obtained by a single - * distance measurement on the device: - * - The measurement status can be read from the #Status. - * - A timing information is given via the #TimeStamp. - * - Information about the frame state is in the #Frame structure. - * - The 1D distance results are gathered under #Bin. - * - The 3D distance results for each pixel is at #Pixels or #Pixel. - * - Auxiliary values such as temperature can be found at #Auxiliary. - * - Raw data from the device is stored in the #Data array. - * . + * distance measurement on the device: + * - The measurement status can be read from the #Status. + * - A timing information is given via the #TimeStamp. + * - Information about the frame state is in the #Frame structure. + * - The 1D distance results are gathered under #Bin. + * - The 3D distance results for each pixel is at #Pixels or #Pixel. + * - Auxiliary values such as temperature can be found at #Auxiliary. + * - Raw data and debug information from the device and API is stored + * in the optional #Debug data structure. Note that this points to + * an optional structure and can be null! + * . * - * The pixel x-y orientation is sketched in the following graph. Note that - * the laser source would be on the right side beyond the reference pixel. - * See also \link argusmap ADC Channel Mapping\endlink + * The pixel x-y orientation is sketched in the following graph. Note that + * the laser source would be on the right side beyond the reference pixel. + * See also \link argus_map ADC Channel Mapping\endlink * @code - * // Pixel Field: Pixel[x][y] - * // - * // 0 -----------> x - * // | O O O O O O O O - * // | O O O O O O O O - * // | O O O O O O O O O (ref. Px) - * // y O O O O O O O O + * // Pixel Field: Pixel[x][y] + * // + * // 0 -----------> x + * // | O O O O O O O O + * // | O O O O O O O O + * // | O O O O O O O O O (ref. Px) + * // y O O O O O O O O * @endcode *****************************************************************************/ -typedef struct { +typedef struct argus_results_t { /*! The \link #status_t status\endlink of the current measurement frame. * - 0 (i.e. #STATUS_OK) for a good measurement signal. * - > 0 for warnings and weak measurement signal. @@ -168,13 +206,6 @@ typedef struct { /*! The configuration for the current measurement frame. */ argus_meas_frame_t Frame; - /*! Raw x-y-sorted ADC results from the device.\n - * Data is arranged as 32-bit values in following order: - * index > phase; where index is pixel number n and auxiliary ADC channel.\n - * Note that disabled pixels are skipped.\n - * e.g. [n=0,p=0][n=0,p=1]..[n=0,p=3][n=1,p=0]...[n=1,p=3]...[n=31,p=3] */ - uint32_t Data[ARGUS_RAW_DATA_VALUES]; - union { /*! Pixel data indexed by channel number n.\n * Contains calibrated range, amplitude and pixel status among others. @@ -183,14 +214,14 @@ typedef struct { * - 0..31: active pixels * - 32: reference pixel * - * See also \link argusmap ADC Channel Mapping\endlink */ + * See also \link argus_map ADC Channel Mapping\endlink */ argus_pixel_t Pixels[ARGUS_PIXELS + 1U]; struct { /*! Pixel data indexed by x-y-indices.\n * The pixels are ordered in a two dimensional array that represent * the x and y indices of the pixel.\n - * See also \link argusmap ADC Channel Mapping\endlink + * See also \link argus_map ADC Channel Mapping\endlink * * Contains calibrated range, amplitude and pixel status among others. */ argus_pixel_t Pixel[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]; @@ -213,8 +244,17 @@ typedef struct { /*! The auxiliary ADC channel data, e.g. sensor temperature. */ argus_results_aux_t Auxiliary; + /*! Optional Debug Data. + * If the pointer is set to a #argus_results_debug_t data structure before + * passing it to the #Argus_EvaluateData function, the data structure is + * filled with internal parameters for debugging purposes. */ + argus_results_debug_t *Debug; + } argus_results_t; /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_RES_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_snm.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_snm.h index 2776308143..2b77965bb0 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_snm.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_snm.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Defines the Shot Noise Monitor (SNM) setup parameters. + * @brief This file is part of the AFBR-S50 API. + * @details Defines the Shot Noise Monitor (SNM) setup parameters. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,30 +36,33 @@ #ifndef ARGUS_SNM_H #define ARGUS_SNM_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup argussnm Shot Noise Monitor - * @ingroup argusapi + * @defgroup argus_snm Shot Noise Monitor + * @ingroup argus_api * - * @brief Shot Noise Monitor (SNM) parameter definitions and API functions. + * @brief Shot Noise Monitor (SNM) parameter definitions and API functions. * - * @details The SNM is an algorithm to monitor and react on shot noise - * induced by harsh environment conditions like high ambient - * light. + * @details The SNM is an algorithm to monitor and react on shot noise + * induced by harsh environment conditions like high ambient + * light. * - * The AFBR-S50 API provides three modes: - * - Dynamic: Automatic mode, automatically adopts to current - * ambient conditions. - * - Static (Outdoor): Static mode, optimized for outdoor applications. - * - Static (Indoor): Static mode, optimized for indoor applications. - * . + * The AFBR-S50 API provides three modes: + * - Dynamic: Automatic mode, automatically adopts to current + * ambient conditions. + * - Static (Outdoor): Static mode, optimized for outdoor applications. + * - Static (Indoor): Static mode, optimized for indoor applications. + * . * - * @addtogroup argussnm + * @addtogroup argus_snm * @{ *****************************************************************************/ /*! The Shot Noise Monitor modes enumeration. */ -typedef enum { +typedef enum argus_snm_mode_t { /*! Static Shot Noise Monitoring Mode, optimized for indoor applications. * Assumes the best case scenario, i.e. no bad influence from ambient conditions. * Thus it uses a fixed setting that will result in the best performance. @@ -79,4 +82,7 @@ typedef enum { /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_SNM_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_status.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_status.h index 244ad1beec..77cd856413 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_status.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_status.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Provides status codes for the AFBR-S50 API. + * @brief This file is part of the AFBR-S50 API. + * @details Provides status codes for the AFBR-S50 API. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,25 +36,32 @@ #ifndef ARGUS_STATUS_H #define ARGUS_STATUS_H +#ifdef __cplusplus +extern "C" { +#endif #include /*!*************************************************************************** - * @defgroup status Status Codes + * @defgroup argus_status Status Codes + * @ingroup argus + * * @brief Status and Error Code Definitions + * * @details Defines status and error codes for function return values. * Basic status number structure: * - 0 is OK or no error. * - negative values determine errors. * - positive values determine warnings or status information. * . - * @addtogroup status + * + * @addtogroup argus_status * @{ *****************************************************************************/ /*!*************************************************************************** - * @brief Type used for all status and error return values. - * @details Basic status number structure: + * @brief Type used for all status and error return values. + * @details Basic status number structure: * - 0 is OK or no error. * - negative values determine errors. * - positive values determine warnings or status information. @@ -138,8 +145,8 @@ enum Status { ********** NVM / Flash Layer Status ********************************************************* *********************************************************************************************/ - /*! -98: Flash Error: The version of the settings in the flash memory is not compatible. */ - ERROR_NVM_INVALID_FILE_VERSION = -98, + /*! -98: Flash Error: The read memory block was not written previously and contains no data. */ + ERROR_NVM_EMPTY = -98, /*! -99: Flash Error: The memory is out of range. */ ERROR_NVM_OUT_OF_RANGE = -99, @@ -183,6 +190,13 @@ enum Status { /*! -102: AFBR-S50 Error: Inconsistent configuration parameters. */ ERROR_ARGUS_INVALID_CFG = -102, + /*! -103: AFBR-S50 Error: The evaluation function has been called but no + * raw data is available yet. + * See also #Argus_EvaluateData for more information. */ + ERROR_ARGUS_BUFFER_EMPTY = -103, + + /*! -104: AFBR-S50 Error: Invalid slave identifier is passed to the module. */ + ERROR_ARGUS_INVALID_SLAVE = -104, /*! -105: AFBR-S50 Error: Invalid measurement mode configuration parameter. */ ERROR_ARGUS_INVALID_MODE = -105, @@ -191,7 +205,6 @@ enum Status { * The current measurement data set is invalid! */ ERROR_ARGUS_BIAS_VOLTAGE_REINIT = -107, - /*! -109: AFBR-S50 Error: The EEPROM readout has failed. The failure is detected * by three distinct read attempts, each resulting in invalid data. * Note: this state differs from that #STATUS_ARGUS_EEPROM_BIT_ERROR @@ -224,7 +237,6 @@ enum Status { * requested command. */ ERROR_ARGUS_BUSY = -191, - /*! -199: AFBR-S50 Error: Unknown module number. */ ERROR_ARGUS_UNKNOWN_MODULE = -199, @@ -235,24 +247,22 @@ enum Status { ERROR_ARGUS_UNKNOWN_LASER = -197, + /*! 191: AFBR-S50 Status (internal): The device is currently busy with testing the + * SPI connection to the device. */ + STATUS_ARGUS_BUSY_TEST = 191, - /*! 193: AFBR-S50 Status (internal): The device is currently busy with updating the - * configuration (i.e. with writing register values). */ - STATUS_ARGUS_BUSY_CFG_UPDATE = 193, - - /*! 194: AFBR-S50 Status (internal): The device is currently busy with updating the - * calibration data (i.e. writing to register values). */ - STATUS_ARGUS_BUSY_CAL_UPDATE = 194, + /*! 192: AFBR-S50 Status (internal): The device is currently busy with updating the + * settings parameter (i.e. with writing register values). */ + STATUS_ARGUS_BUSY_UPDATE = 192, /*! 195: AFBR-S50 Status (internal): The device is currently executing a calibration - * sequence. */ + * sequence. */ STATUS_ARGUS_BUSY_CAL_SEQ = 195, /*! 196: AFBR-S50 Status (internal): The device is currently executing a measurement * cycle. */ STATUS_ARGUS_BUSY_MEAS = 196, - /*! 100: AFBR-S50 Status (internal): The ASIC is initializing a new measurement, i.e. * a register value is written that starts an integration cycle on the ASIC. */ STATUS_ARGUS_STARTING = 100, @@ -260,9 +270,10 @@ enum Status { /*! 103: AFBR-S50 Status (internal): The ASIC is performing an integration cycle. */ STATUS_ARGUS_ACTIVE = 103, - - }; /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_STATUS_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_version.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_version.h index ea16342c84..a1a2d878ac 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_version.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_version.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file contains the current API version number. + * @brief This file is part of the AFBR-S50 API. + * @details This file contains the current API version number. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,16 +36,19 @@ #ifndef ARGUS_VERSION_H #define ARGUS_VERSION_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup version API Version - * @ingroup argusapi + * @defgroup argus_version API Version + * @ingroup argus_api * - * @brief API and library core version number + * @brief API and library core version number * - * @details Contains the AFBR-S50 API and Library Core Version Number. + * @details Contains the AFBR-S50 API and Library Core Version Number. * - * @addtogroup version + * @addtogroup argus_version * @{ *****************************************************************************/ @@ -53,13 +56,13 @@ #define ARGUS_API_VERSION_MAJOR 1 /*! Minor version number of the AFBR-S50 API. */ -#define ARGUS_API_VERSION_MINOR 3 +#define ARGUS_API_VERSION_MINOR 4 /*! Bugfix version number of the AFBR-S50 API. */ -#define ARGUS_API_VERSION_BUGFIX 5 +#define ARGUS_API_VERSION_BUGFIX 4 -/*! Build version nunber of the AFBR-S50 API. */ -#define ARGUS_API_VERSION_BUILD "20210812171515" +/*! Build version number of the AFBR-S50 API. */ +#define ARGUS_API_VERSION_BUILD "20230327150535" /*****************************************************************************/ @@ -73,4 +76,7 @@ (ARGUS_API_VERSION_BUGFIX)) /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_VERSION_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_xtalk.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_xtalk.h index 5613706267..6f3d40b49a 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_xtalk.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_xtalk.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 hardware API. - * @details Defines the generic device calibration API. + * @brief This file is part of the AFBR-S50 hardware API. + * @details Defines the generic device calibration API. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,44 +36,71 @@ #ifndef ARGUS_XTALK_H #define ARGUS_XTALK_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @addtogroup arguscal + * @addtogroup argus_cal * @{ *****************************************************************************/ -#include "api/argus_def.h" +#include "argus_def.h" +#include "argus_dfm.h" /*!*************************************************************************** - * @brief Pixel Crosstalk Compensation Vector. - * @details Contains calibration data (per pixel) that belongs to the - * RX-TX-Crosstalk compensation feature. + * @brief Pixel Crosstalk Compensation Vector. + * @details Contains calibration data (per pixel) that belongs to the + * RX-TX-Crosstalk compensation feature. + * The crosstalk vector consists of a Sine and Cosine component in LSB. *****************************************************************************/ - -/*! Pixel Crosstalk Vector */ -typedef struct { +typedef struct xtalk_t { /*! Crosstalk Vector - Sine component. + * Units: LSB * Special Value: Q11_4_MIN == not available */ q11_4_t dS; /*! Crosstalk Vector - Cosine component. + * Units: LSB * Special Value: Q11_4_MIN == not available */ q11_4_t dC; } xtalk_t; /*!*************************************************************************** - * @brief Pixel-To-Pixel Crosstalk Compensation Parameters. - * @details Contains calibration data that belongs to the pixel-to-pixel - * crosstalk compensation feature. + * @brief Pixel Crosstalk Vector Table. + * @details Contains crosstalk vector values for all 32 active pixels, + * separated for A/B-Frames. *****************************************************************************/ -typedef struct { +typedef struct argus_cal_xtalk_table_t { + union { + struct { + /*! The crosstalk vector table for A-Frames. */ + xtalk_t FrameA[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]; + + /*! The crosstalk vector table for B-Frames. */ + xtalk_t FrameB[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]; + }; + + /*! The crosstalk vector table for A/B-Frames of all 32 pixels.*/ + xtalk_t Table[ARGUS_DFM_FRAME_COUNT][ARGUS_PIXELS_X][ARGUS_PIXELS_Y]; + }; + +} argus_cal_xtalk_table_t; + + +/*!*************************************************************************** + * @brief Pixel-To-Pixel Crosstalk Compensation Parameters. + * @details Contains calibration data that belongs to the pixel-to-pixel + * crosstalk compensation feature. + *****************************************************************************/ +typedef struct argus_cal_p2pxtalk_t { /*! Pixel-To-Pixel Compensation on/off. */ bool Enabled; /*! The relative threshold determines when the compensation is active for * each individual pixel. The value determines the ratio of the individual - * pixel signal is with respect to the overall average signal. If the + * pixel signal with respect to the overall average signal. If the * ratio is smaller than the value, the compensation is active. Absolute * and relative conditions are connected with AND logic. */ uq0_8_t RelativeThreshold; @@ -111,4 +138,7 @@ typedef struct { /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_XTALK_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/argus.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/argus.h index dcea881d02..79cf0ede58 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/argus.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/argus.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file the main header of the AFBR-S50 API. + * @brief This file is part of the AFBR-S50 API. + * @details This file the main header of the AFBR-S50 API. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,7 +36,6 @@ #ifndef ARGUS_H #define ARGUS_H - #ifdef __cplusplus extern "C" { #endif @@ -44,7 +43,6 @@ extern "C" { #include "api/argus_api.h" #ifdef __cplusplus -} +} // extern "C" #endif - #endif /* ARGUS_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_irq.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_irq.h index 3eb7d2cfd4..d55097cd8a 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_irq.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_irq.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file provides an interface for enabling/disabling interrupts. + * @brief This file is part of the AFBR-S50 API. + * @details This file provides an interface for enabling/disabling interrupts. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,96 +36,94 @@ #ifndef ARGUS_IRQ_H #define ARGUS_IRQ_H - #ifdef __cplusplus extern "C" { #endif /*!*************************************************************************** - * @defgroup argus_irq IRQ: Global Interrupt Control Layer - * @ingroup argus_platform + * @defgroup argus_irq IRQ: Global Interrupt Control Layer + * @ingroup argus_hal * - * @brief Global Interrupt Control Layer + * @brief Global Interrupt Control Layer * - * @details This module provides functionality to globally enable/disable - * interrupts in a nested way. + * @details This module provides functionality to globally enable/disable + * interrupts in a nested way. * - * Here is a simple example implementation using the CMSIS functions - * "__enable_irq()" and "__disable_irq()". An integer counter is - * used to achieve nested interrupt disabling: + * Here is a simple example implementation using the CMSIS functions + * "__enable_irq()" and "__disable_irq()". An integer counter is + * used to achieve nested interrupt disabling: * - * @code + * @code * - * // Global lock level counter value. - * static volatile int g_irq_lock_ct; + * // Global lock level counter value. + * static volatile int g_irq_lock_ct; * - * // Global unlock all interrupts using CMSIS function "__enable_irq()". - * void IRQ_UNLOCK(void) - * { - * assert(g_irq_lock_ct > 0); - * if (--g_irq_lock_ct <= 0) - * { - * g_irq_lock_ct = 0; - * __enable_irq(); - * } - * } + * // Global unlock all interrupts using CMSIS function "__enable_irq()". + * void IRQ_UNLOCK(void) + * { + * assert(g_irq_lock_ct > 0); + * if (--g_irq_lock_ct <= 0) + * { + * g_irq_lock_ct = 0; + * __enable_irq(); + * } + * } * - * // Global lock all interrupts using CMSIS function "__disable_irq()". - * void IRQ_LOCK(void) - * { - * __disable_irq(); - * g_irq_lock_ct++; - * } + * // Global lock all interrupts using CMSIS function "__disable_irq()". + * void IRQ_LOCK(void) + * { + * __disable_irq(); + * g_irq_lock_ct++; + * } * - * @endcode + * @endcode * - * @note The IRQ locking mechanism is used to create atomic sections - * (within the scope of the AFBR-S50 API) that are very few processor - * instruction only. It does NOT lock interrupts for considerable - * amounts of time. + * @note The IRQ locking mechanism is used to create atomic sections + * (within the scope of the AFBR-S50 API) that are very few processor + * instruction only. It does NOT lock interrupts for considerable + * amounts of time. * - * @note The IRQ_LOCK might get called multiple times. Therefore, the - * API expects that the IRQ_UNLOCK must be called as many times as - * the IRQ_LOCK was called before the interrupts are enabled. + * @note The IRQ_LOCK might get called multiple times. Therefore, the + * API expects that the IRQ_UNLOCK must be called as many times as + * the IRQ_LOCK was called before the interrupts are enabled. * - * @note The interrupts utilized by the AFBR-S50 API can be interrupted - * by other, higher prioritized interrupts, e.g. some system - * critical interrupts. In this case, the IRQ_LOCK/IRQ_UNLOCK - * mechanism can be implemented such that only the interrupts - * required for the AFBR-S50 API are locked. The above example is - * dedicated to a ARM Corex-M0 architecture, where interrupts - * can only disabled at a global scope. Other architectures like - * ARM Cortex-M4 allow selective disabling of interrupts. + * @note The interrupts utilized by the AFBR-S50 API can be interrupted + * by other, higher prioritized interrupts, e.g. some system + * critical interrupts. In this case, the IRQ_LOCK/IRQ_UNLOCK + * mechanism can be implemented such that only the interrupts + * required for the AFBR-S50 API are locked. The above example is + * dedicated to a ARM Corex-M0 architecture, where interrupts + * can only disabled at a global scope. Other architectures like + * ARM Cortex-M4 allow selective disabling of interrupts. * - * @addtogroup argus_irq + * @addtogroup argus_irq * @{ *****************************************************************************/ /*!*************************************************************************** - * @brief Enable IRQ Interrupts + * @brief Enable IRQ Interrupts * - * @details Enables IRQ interrupts and enters an atomic or critical section. + * @details Enables IRQ interrupts and enters an atomic or critical section. * - * @note The IRQ_LOCK might get called multiple times. Therefore, the - * API expects that the IRQ_UNLOCK must be called as many times as - * the IRQ_LOCK was called before the interrupts are enabled. + * @note The IRQ_LOCK might get called multiple times. Therefore, the + * API expects that the IRQ_UNLOCK must be called as many times as + * the IRQ_LOCK was called before the interrupts are enabled. *****************************************************************************/ void IRQ_UNLOCK(void); /*!*************************************************************************** - * @brief Disable IRQ Interrupts + * @brief Disable IRQ Interrupts * - * @details Disables IRQ interrupts and leaves the atomic or critical section. + * @details Disables IRQ interrupts and leaves the atomic or critical section. * - * @note The IRQ_LOCK might get called multiple times. Therefore, the - * API expects that the IRQ_UNLOCK must be called as many times as - * the IRQ_LOCK was called before the interrupts are enabled. + * @note The IRQ_LOCK might get called multiple times. Therefore, the + * API expects that the IRQ_UNLOCK must be called as many times as + * the IRQ_LOCK was called before the interrupts are enabled. *****************************************************************************/ void IRQ_LOCK(void); -#ifdef __cplusplus -} -#endif - /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif // ARGUS_IRQ_H diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_nvm.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_nvm.h index 69939b7759..b8150f0281 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_nvm.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_nvm.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file provides an interface for the optional non-volatile memory. + * @brief This file is part of the AFBR-S50 API. + * @details This file provides an interface for the optional non-volatile memory. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,99 +36,113 @@ #ifndef ARGUS_NVM_H #define ARGUS_NVM_H - #ifdef __cplusplus extern "C" { #endif /*!*************************************************************************** - * @defgroup argus_nvm NVM: Non-Volatile Memory Layer - * @ingroup argus_platform + * @defgroup argus_nvm NVM: Non-Volatile Memory Layer + * @ingroup argus_hal * - * @brief Non-Volatile Memory Layer + * @brief Non-Volatile Memory Layer * - * @details This module provides functionality to access the non-volatile - * memory (e.g. flash) on the underlying platform. + * @details This module provides functionality to access the non-volatile + * memory (e.g. flash) on the underlying platform. * - * This module is optional and only required if calibration data - * needs to be stored within the API. + * This module is optional and only required if calibration data + * needs to be stored within the API. * - * @note The implementation of this module is optional for the correct - * execution of the API. If not implemented, a weak implementation - * within the API will be used that disables the NVM feature. + * @note The implementation of this module is optional for the correct + * execution of the API. If not implemented, a weak implementation + * within the API will be used that disables the NVM feature. * - * @addtogroup argus_nvm + * @addtogroup argus_nvm * @{ *****************************************************************************/ -#include "argus.h" +#include "api/argus_def.h" + +/*! The NVM block size in the non-volatile memory. */ +#define ARGUS_NVM_BLOCK_SIZE 0x300 // 768 bytes /*!*************************************************************************** - * @brief Initializes the non-volatile memory unit and reserves a chunk of memory. + * @brief Write a block of data to the non-volatile memory. * - * @details The function is called upon API initialization sequence. If available, - * the non-volatile memory module reserves a chunk of memory with the - * provides number of bytes (size) and returns with #STATUS_OK. + * @details The function is called whenever the API wants to write data into + * non-volatile memory, e.g. flash. Later, the API reads the written + * data via the #NVM_ReadBlock function. * - * If not implemented, the function should return #ERROR_NOT_IMPLEMENTED - * in oder to inform the API to not use the NVM module. + * The data shall be written to a specified memory block that is + * uniquely dedicated to each individual device. The /p id parameter + * is passed to the function that identifies the device. The /p id + * is composed of the device ID and module type, i.e. it is unique + * among all devices. If only a single device is used anyway, the + * /p id parameter can be ignored. * - * After initialization, the API calls the #NVM_Write and #NVM_Read - * methods to write within the reserved chunk of memory. + * If no NVM module is available, the function can return with error + * #ERROR_NOT_IMPLEMENTED and the API ignores the NVM. * - * @note The implementation of this function is optional for the correct - * execution of the API. If not implemented, a weak implementation - * within the API will be used that disables the NVM feature. + * If write fails, e.g. due to lack of memory, a negative status + * must be returned, e.g. #ERROR_NVM_OUT_OF_RANGE. * - * @param size The required size of NVM to store all parameters. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * The block size is fixed for a single device. The actual block size + * is defined with #ARGUS_NVM_BLOCK_SIZE. + * + * @note The implementation of this function is optional for the correct + * execution of the API. If not implemented, a weak implementation + * within the API will be used that disables the NVM feature. + * + * @param id The 32-bit ID number to identify the corresponding memory block. + * @param block_size The number of bytes to be written. Note that this value + * is fixed, i.e. the API always writes the same data size. + * The size is defined here: #ARGUS_NVM_BLOCK_SIZE. + * @param buf The pointer to the data buffer of size /p block_size that needs + * to be written to the NVM. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t NVM_Init(uint32_t size); +status_t NVM_WriteBlock(uint32_t id, uint32_t block_size, uint8_t const *buf); /*!*************************************************************************** - * @brief Write a block of data to the non-volatile memory. + * @brief Reads a block of data from the non-volatile memory. * - * @details The function is called whenever the API wants to write data into - * the previously reserved (#NVM_Init) memory block. The data shall - * be written at a given offset and with a given size. + * @details The function is called whenever the API wants to read data from + * non-volatile memory, e.g. flash. The data will be previously + * stored using the #NVM_WriteBlock function. Otherwise, the function + * must return a corresponding error code, namely #ERROR_NVM_EMPTY. * - * If no NVM module is available, the function can return with error - * #ERROR_NOT_IMPLEMENTED. + * The data shall be read from a specified memory block that is + * uniquely dedicated to each individual device. The /p id parameter + * is passed to the function that identifies the device. The /p id + * is composed of the device ID and module type, i.e. it is unique + * among all devices. If only a single device is used anyway, the + * /p id parameter can be ignored. * - * @note The implementation of this function is optional for the correct - * execution of the API. If not implemented, a weak implementation - * within the API will be used that disables the NVM feature. + * If no NVM module is available, the function can return with error + * #ERROR_NOT_IMPLEMENTED and the API ignores the NVM. * - * @param offset The index offset where the first byte needs to be written. - * @param size The number of bytes to be written. - * @param buf The pointer to the data buffer with the data to be written. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * If read fails, e.g. if data has not been written previously, + * a negative status must be returned, e.g. #ERROR_NVM_EMPTY if no + * data has been written yet or any other negative error else-wise. + * + * The block size is fixed for a single device. The actual block size + * is defined with #ARGUS_NVM_BLOCK_SIZE. + * + * @note The implementation of this function is optional for the correct + * execution of the API. If not implemented, a weak implementation + * within the API will be used that disables the NVM feature. + * + * @param id The 32-bit ID number to identify the corresponding memory block. + * @param block_size The number of bytes to be read. Note that this value + * is fixed, i.e. the API always reads the same data size. + * The size is defined here: #ARGUS_NVM_BLOCK_SIZE. + * @param buf The pointer to the data buffer of size /p block_size to copy + * the data to. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t NVM_Write(uint32_t offset, uint32_t size, uint8_t const *buf); - -/*!*************************************************************************** - * @brief Reads a block of data from the non-volatile memory. - * - * @details The function is called whenever the API wants to read data from - * the previously reserved (#NVM_Init) memory block. The data shall - * be read at a given offset and with a given size. - * - * If no NVM module is available, the function can return with error - * #ERROR_NOT_IMPLEMENTED. - * - * @note The implementation of this function is optional for the correct - * execution of the API. If not implemented, a weak implementation - * within the API will be used that disables the NVM feature. - * - * @param offset The index offset where the first byte needs to be read. - * @param size The number of bytes to be read. - * @param buf The pointer to the data buffer to copy the data to. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). - *****************************************************************************/ -status_t NVM_Read(uint32_t offset, uint32_t size, uint8_t *buf); -#ifdef __cplusplus -} -#endif +status_t NVM_ReadBlock(uint32_t id, uint32_t block_size, uint8_t *buf); /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif // ARGUS_NVM_H diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_print.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_print.h index 0ca49f858c..9a76dbe485 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_print.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_print.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file provides an interface for the optional debug module. + * @brief This file is part of the AFBR-S50 API. + * @details This file provides an interface for the optional debug module. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,48 +36,55 @@ #ifndef ARGUS_PRINT_H #define ARGUS_PRINT_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup argus_log Debug: Logging Interface - * @ingroup argus_platform + * @defgroup argus_log Debug: Logging Interface + * @ingroup argus_hal * - * @brief Logging interface for the AFBR-S50 API. + * @brief Logging interface for the AFBR-S50 API. * - * @details This interface provides logging utility functions. - * Defines a printf-like function that is used to print error and - * log messages. + * @details This interface provides logging utility functions. + * Defines a printf-like function that is used to print error and + * log messages. * - * @addtogroup argus_log + * @addtogroup argus_log * @{ *****************************************************************************/ #include "api/argus_def.h" /*!*************************************************************************** - * @brief A printf-like function to print formatted data to an debugging interface. + * @brief A printf-like function to print formatted data to an debugging interface. * * @details Writes the C string pointed by fmt_t to an output. If format - * includes format specifiers (subsequences beginning with %), the - * additional arguments following fmt_t are formatted and inserted in - * the resulting string replacing their respective specifiers. + * includes format specifiers (subsequences beginning with %), the + * additional arguments following fmt_t are formatted and inserted in + * the resulting string replacing their respective specifiers. * - * To enable the print functionality, an implementation of the function - * must be provided that maps the output to an interface like UART or - * a debugging console, e.g. by forwarding to standard printf() method. + * To enable the print functionality, an implementation of the function + * must be provided that maps the output to an interface like UART or + * a debugging console, e.g. by forwarding to standard printf() method. * - * @note The implementation of this function is optional for the correct - * execution of the API. If not implemented, a weak implementation - * within the API will be used that does nothing. This will improve - * the performance but no error messages are logged. + * @note The implementation of this function is optional for the correct + * execution of the API. If not implemented, a weak implementation + * within the API will be used that does nothing. This will improve + * the performance but no error messages are logged. * - * @note The naming is different from the standard printf() on purpose to - * prevent builtin compiler optimizations. + * @note The naming is different from the standard printf() on purpose to + * prevent builtin compiler optimizations. * - * @param fmt_s The usual print() format string. - * @param ... The usual print() parameters. * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param fmt_s The usual print() format string. + * @param ... The usual print() parameters. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t print(const char *fmt_s, ...); /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_PRINT_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_s2pi.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_s2pi.h index 4b8f5331b5..8d912afc22 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_s2pi.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_s2pi.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file provides an interface for the required S2PI module. + * @brief This file is part of the AFBR-S50 API. + * @details This file provides an interface for the required S2PI module. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,95 +36,94 @@ #ifndef ARGUS_S2PI_H #define ARGUS_S2PI_H - #ifdef __cplusplus extern "C" { #endif /*!*************************************************************************** - * @defgroup argus_s2pi S2PI: Serial Peripheral Interface - * @ingroup argus_platform + * @defgroup argus_s2pi S2PI: Serial Peripheral Interface + * @ingroup argus_hal * - * @brief S2PI: SPI incl. GPIO Hardware Layer Module + * @brief S2PI: SPI incl. GPIO Hardware Layer Module * - * @details The S2PI module consists of a standard SPI interface plus a - * single GPIO interrupt line. Furthermore, the SPI pins are - * accessible via GPIO control to allow a software emulation of - * additional protocols using the same pins. + * @details The S2PI module consists of a standard SPI interface plus a + * single GPIO interrupt line. Furthermore, the SPI pins are + * accessible via GPIO control to allow a software emulation of + * additional protocols using the same pins. * - * **SPI interface:** + * **SPI interface:** * - * The SPI interface is based around a single functionality: + * The SPI interface is based around a single functionality: * - * #S2PI_TransferFrame. This function transfers a specified number - * of bytes via the interfaces MOSI line and simultaneously reads - * the incoming data on the MOSI line. The read can also be skipped. - * The transfer happen asynchronously, e.g. via a DMA request. After - * finishing the transfer, the provided callback is invoked with - * the status of the transfer and the provided abstract parameter. - * Furthermore, the functions receives a slave parameter that can - * be used to connect multiple slaves, each with its individual - * chip select line. + * #S2PI_TransferFrame. This function transfers a specified number + * of bytes via the interfaces MOSI line and simultaneously reads + * the incoming data on the MOSI line. The read can also be skipped. + * The transfer happen asynchronously, e.g. via a DMA request. After + * finishing the transfer, the provided callback is invoked with + * the status of the transfer and the provided abstract parameter. + * Furthermore, the functions receives a slave parameter that can + * be used to connect multiple slaves, each with its individual + * chip select line. * - * The interface also provides functionality to change the SPI - * baud rate. An additional abort method is used to cancel the - * ongoing transfer. + * The interface also provides functionality to change the SPI + * baud rate. An additional abort method is used to cancel the + * ongoing transfer. * - * **GPIO interface:** + * **GPIO interface:** * - * The GPIO part of the S2PI interface has two distinct concerns: + * The GPIO part of the S2PI interface has two distinct concerns: * - * First, the GPIO interface handles the measurement finished interrupt - * from the device. When the device invokes the interrupt, it pulls - * the interrupt line to low. Thus the interrupt must trigger when - * a transition from high to low occurs on the interrupt line. + * First, the GPIO interface handles the measurement finished interrupt + * from the device. When the device invokes the interrupt, it pulls + * the interrupt line to low. Thus the interrupt must trigger when + * a transition from high to low occurs on the interrupt line. * - * The module simply invokes a callback when this interrupt occurs. - * The #S2PI_SetIrqCallback method is used to install the callback - * for a specified slave. Each slave will have its own interrupt - * line. An additional callback parameter can be set that would be - * passed to the callback function. + * The module simply invokes a callback when this interrupt occurs. + * The #S2PI_SetIrqCallback method is used to install the callback + * for a specified slave. Each slave will have its own interrupt + * line. An additional callback parameter can be set that would be + * passed to the callback function. * - * In addition to the interrupt, all SPI pins need to be accessible - * as GPIO pins through this interface. This is required to read - * the EEPROM memory on the device hat is connected to the SPI - * pins but requires a different protocol that is not compatible - * to any standard SPI interface. Therefore, the interface provides - * the possibility to switch to GPIO control mode that allows to - * emulate the EEPROM protocol via software bit banging. + * In addition to the interrupt, all SPI pins need to be accessible + * as GPIO pins through this interface. This is required to read + * the EEPROM memory on the device hat is connected to the SPI + * pins but requires a different protocol that is not compatible + * to any standard SPI interface. Therefore, the interface provides + * the possibility to switch to GPIO control mode that allows to + * emulate the EEPROM protocol via software bit banging. * - * Two methods are provided to switch forth and back between SPI - * and GPIO control. In GPIO mode, several functions are used to - * read and write the individual GPIO pins. + * Two methods are provided to switch forth and back between SPI + * and GPIO control. In GPIO mode, several functions are used to + * read and write the individual GPIO pins. * - * Note that the GPIO mode is only required to readout the EEPROM - * upon initialization of the device, i.e. during execution of the - * #Argus_Init or #Argus_Reinit methods. The GPIO mode is not used - * during measurements. + * Note that the GPIO mode is only required to readout the EEPROM + * upon initialization of the device, i.e. during execution of the + * #Argus_Init or #Argus_Reinit methods. The GPIO mode is not used + * during measurements. * * - * @addtogroup argus_s2pi + * @addtogroup argus_s2pi * @{ *****************************************************************************/ #include "api/argus_def.h" /*!*************************************************************************** - * @brief S2PI layer callback function type for the SPI transfer completed event. + * @brief S2PI layer callback function type for the SPI transfer completed event. * - * @param status The \link #status_t status\endlink of the completed + * @param status The \link #status_t status\endlink of the completed * transfer (#STATUS_OK on success). * - * @param param The provided (optional, can be null) callback parameter. + * @param param The provided (optional, can be null) callback parameter. * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ typedef status_t (*s2pi_callback_t)(status_t status, void *param); /*!*************************************************************************** - * @brief S2PI layer callback function type for the GPIO interrupt event. + * @brief S2PI layer callback function type for the GPIO interrupt event. * - * @param param The provided (optional, can be null) callback parameter. + * @param param The provided (optional, can be null) callback parameter. *****************************************************************************/ typedef void (*s2pi_irq_callback_t)(void *param); @@ -132,8 +131,8 @@ typedef void (*s2pi_irq_callback_t)(void *param); * can be used to identify the slave within the SPI module. */ typedef int32_t s2pi_slave_t; -/*! The enumeration of S2PI pins. */ -typedef enum { +/*! The enumeration of S2PI pins. */ +typedef enum s2pi_pin_t { /*! The SPI clock pin. */ S2PI_CLK, @@ -153,64 +152,141 @@ typedef enum { /*!*************************************************************************** - * @brief Returns the status of the SPI module. + * @brief Returns the status of the SPI module. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_IDLE: No SPI transfer or GPIO access is ongoing. - * - #STATUS_BUSY: An SPI transfer is in progress. - * - #STATUS_S2PI_GPIO_MODE: The module is in GPIO mode. + * @param slave The specified S2PI slave. Note that the slave information is + * only required if multiple SPI instances are used in order to + * map to the correct SPI instance. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_IDLE: No SPI transfer or GPIO access is ongoing. + * - #STATUS_BUSY: An SPI transfer is in progress. + * - #STATUS_S2PI_GPIO_MODE: The module is in GPIO mode. *****************************************************************************/ -status_t S2PI_GetStatus(void); +status_t S2PI_GetStatus(s2pi_slave_t slave); /*!*************************************************************************** - * @brief Transfers a single SPI frame asynchronously. + * @brief Tries to grab the SPI interface mutex for the next transfer. + * + * @details This mutex prevents new asynchronous SPI requests to interfere + * with transfers already in progress for this interface. + * + * Note that this is only required if multiple device are connected to + * a single SPI interface. If only operating a single device per SPI, + * the function can simply always return #STATUS_OK. + * + * There must be a dedicated mutex object per SPI interface if + * multiple SPI interfaces are used. + * + * The mutex will be released in the #S2PI_ReleaseMutex function. + * See #S2PI_ReleaseMutex for additional information. + * + * Here is a simple example implementation for the multiple devices on + * a single SPI interface case. Note that the SpiMutexBlocked must be + * defined per SPI interface if multiple SPI interfaces are used. + * + * @code + * static volatile bool SpiMutexBlocked = false; + * status_t S2PI_TryGetMutex(s2pi_slave_t slave) + * { + * (void) slave; // not used in this implementation as all + * // SPI slaves are on the same SPI interface + * + * status_t status = STATUS_BUSY; + * IRQ_LOCK(); + * if (!SpiMutexBlocked) + * { + * SpiMutexBlocked = true; + * status = STATUS_OK; + * } + * IRQ_UNLOCK(); + * return status; + * } + * void S2PI_ReleaseMutex(s2pi_slave_t slave) + * { + * (void) slave; // not used in this implementation + * SpiMutexBlocked = false; + * } + * @endcode + * + * @param slave The specified S2PI slave. Note that the slave information is + * only required if multiple SPI instances are used in order to + * map to the correct SPI instance. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK: the SPI interface was successfully reserved for the caller + * - #STATUS_BUSY: another transfer is ongoing, the caller must not access the bus + *****************************************************************************/ +status_t S2PI_TryGetMutex(s2pi_slave_t slave); + +/*!*************************************************************************** + * @brief Releases the SPI interface mutex. + * + * @details Once the mutex is captured, only a single thread (the one that + * captured it) will call this release function, so there is no + * need for any test or thread safe barriers. Also there is no + * side effect of calling this function when the Mutex is not + * taken so this function can be really simple and doesn't need + * to return anything. + * + * See #S2PI_TryGetMutex on more information and an example + * implementation for the single SPI interface case. + * + * @param slave The specified S2PI slave. Note that the slave information is + * only required if multiple SPI instances are used in order to + * map to the correct SPI instance. + *****************************************************************************/ +void S2PI_ReleaseMutex(s2pi_slave_t slave); + +/*!*************************************************************************** + * @brief Transfers a single SPI frame asynchronously. * * @details Transfers a single SPI frame in asynchronous manner. The Tx data - * buffer is written to the device via the MOSI line. - * Optionally, the data on the MISO line is written to the provided - * Rx data buffer. If null, the read data is dismissed. Note that - * Rx and Tx buffer can be identical. I.e. the same buffer is used - * for writing and reading data. First, a byte is transmitted and - * the received byte overwrites the previously send value. + * buffer is written to the device via the MOSI line. + * Optionally, the data on the MISO line is written to the provided + * Rx data buffer. If null, the read data is dismissed. Note that + * Rx and Tx buffer can be identical. I.e. the same buffer is used + * for writing and reading data. First, a byte is transmitted and + * the received byte overwrites the previously send value. * - * The transfer of a single frame requires to not toggle the chip - * select line to high in between the data frame. The maximum - * number of bytes transfered in a single SPI transfer is given by - * the data value register of the device, which is 396 data bytes - * plus a single address byte: 397 bytes. + * The transfer of a single frame requires to not toggle the chip + * select line to high in between the data frame. The maximum + * number of bytes transferred in a single SPI transfer is given by + * the data value register of the device, which is 396 data bytes + * plus a single address byte: 397 bytes. * - * An optional callback is invoked when the asynchronous transfer - * is finished. If the \p callback parameter is a null pointer, - * no callback is provided. Note that the provided buffer must not - * change while the transfer is ongoing. + * An optional callback is invoked when the asynchronous transfer + * is finished. If the \p callback parameter is a null pointer, + * no callback is provided. Note that the provided buffer must not + * change while the transfer is ongoing. * - * Use the slave parameter to determine the corresponding slave via the - * given chip select line. + * Use the slave parameter to determine the corresponding slave via the + * given chip select line. * - * Usually, two distinct interrupts are required to handle the RX and - * TX ready events. The callback must be invoked from whichever - * interrupt comes after the SPI transfer has been finished. Note - * that new SPI transfers are invoked from within the callback function - * (i.e. from within the interrupt service routine of same priority). + * Usually, two distinct interrupts are required to handle the RX and + * TX ready events. The callback must be invoked from whichever + * interrupt comes after the SPI transfer has been finished. Note + * that new SPI transfers are invoked from within the callback function + * (i.e. from within the interrupt service routine of same priority). * - * @param slave The specified S2PI slave. - * @param txData The 8-bit values to write to the SPI bus MOSI line. - * @param rxData The 8-bit values received from the SPI bus MISO line + * @param slave The specified S2PI slave. + * @param txData The 8-bit values to write to the SPI bus MOSI line. + * @param rxData The 8-bit values received from the SPI bus MISO line * (pass a null pointer if the data don't need to be read). - * @param frameSize The number of 8-bit values to be sent/received. - * @param callback A callback function to be invoked when the transfer is - * finished. Pass a null pointer if no callback is required. - * @param callbackData A pointer to a state that will be passed to the + * @param frameSize The number of 8-bit values to be sent/received. + * @param callback A callback function to be invoked when the transfer is + * finished. Pass a null pointer if no callback is required. + * @param callbackData A pointer to a state that will be passed to the * callback. Pass a null pointer if not used. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK: Successfully invoked the transfer. - * - #ERROR_INVALID_ARGUMENT: An invalid parameter has been passed. - * - #ERROR_S2PI_INVALID_SLAVE: A wrong slave identifier is provided. - * - #STATUS_BUSY: An SPI transfer is already in progress. The - * transfer was not started. - * - #STATUS_S2PI_GPIO_MODE: The module is in GPIO mode. The transfer - * was not started. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK: Successfully invoked the transfer. + * - #ERROR_INVALID_ARGUMENT: An invalid parameter has been passed. + * - #ERROR_S2PI_INVALID_SLAVE: A wrong slave identifier is provided. + * - #STATUS_BUSY: An SPI transfer is already in progress. The + * transfer was not started. + * - #STATUS_S2PI_GPIO_MODE: The module is in GPIO mode. The transfer + * was not started. *****************************************************************************/ status_t S2PI_TransferFrame(s2pi_slave_t slave, uint8_t const *txData, @@ -220,136 +296,158 @@ status_t S2PI_TransferFrame(s2pi_slave_t slave, void *callbackData); /*!*************************************************************************** - * @brief Terminates a currently ongoing asynchronous SPI transfer. + * @brief Terminates a currently ongoing asynchronous SPI transfer. * - * @details When a callback is set for the current ongoing activity, it is - * invoked with the #ERROR_ABORTED error byte. + * @details When a callback is set for the current ongoing activity, it is + * invoked with the #ERROR_ABORTED error byte. * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param slave The specified S2PI slave. Note that the slave information is + * only required if multiple SPI instances are used in order to + * map to the correct SPI instance. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t S2PI_Abort(void); +status_t S2PI_Abort(s2pi_slave_t slave); /*!*************************************************************************** - * @brief Set a callback for the GPIO IRQ for a specified S2PI slave. + * @brief Set a callback for the GPIO IRQ for a specified S2PI slave. * - * @param slave The specified S2PI slave. - * @param callback A callback function to be invoked when the specified - * S2PI slave IRQ occurs. Pass a null pointer to disable - * the callback. - * @param callbackData A pointer to a state that will be passed to the + * @param slave The specified S2PI slave. + * @param callback A callback function to be invoked when the specified + * S2PI slave IRQ occurs. Pass a null pointer to disable + * the callback. + * @param callbackData A pointer to a state that will be passed to the * callback. Pass a null pointer if not used. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK: Successfully installation of the callback. - * - #ERROR_S2PI_INVALID_SLAVE: A wrong slave identifier is provided. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK: Successfully installation of the callback. + * - #ERROR_S2PI_INVALID_SLAVE: A wrong slave identifier is provided. *****************************************************************************/ status_t S2PI_SetIrqCallback(s2pi_slave_t slave, s2pi_irq_callback_t callback, void *callbackData); /*!*************************************************************************** - * @brief Reads the current status of the IRQ pin. + * @brief Reads the current interrupt pending status of the IRQ pin. * * @details In order to keep a low priority for GPIO IRQs, the state of the - * IRQ pin must be read in order to reliable check for chip timeouts. + * IRQ pin must be read in order to reliable check for chip timeouts. * - * The execution of the interrupt service routine for the data-ready - * interrupt from the corresponding GPIO pin might be delayed due to - * priority issues. The delayed execution might disable the timeout - * for the eye-safety checker too late causing false error messages. - * In order to overcome the issue, the state of the IRQ GPIO input - * pin is read before raising a timeout error in order to check if - * the device has already finished but the IRQ is still pending to be - * executed! + * The execution of the interrupt service routine for the data-ready + * interrupt from the corresponding GPIO pin might be delayed due to + * priority issues. The delayed execution might disable the timeout + * for the eye-safety checker too late causing false error messages. + * In order to overcome the issue, the interrupt state of the IRQ + * GPIO input pin is read before raising a timeout error in order to + * check if the device has already finished and the IRQ is still + * pending to be executed! + * + * Note: an easy implementation is to simply return the state of the + * IRQ line, i.e. 0 if there is a low input state and 1 if there is + * a high input state on the IRQ input pin. However, this + * implementation is not fully reliable since the GPIO interrupt + * (triggered on the falling edge) might be missed and the callback + * is never invoked while the IRQ line is correctly asserted to low + * state by the device. In that case, the API is waiting forever + * until the callback is invoked which might never happen. Therefore, + * it is better if the implementation checks the state of the IRQ + * pending status register or even combines both variations. - * @param slave The specified S2PI slave. - * @return Returns 1U if the IRQ pin is high (IRQ not pending) and 0U if the - * devices pulls the pin to low state (IRQ pending). + * @param slave The specified S2PI slave. + * + * @return Returns 1U if the IRQ is NOT pending (pin is in high state) and + * 0U if the IRQ is pending (pin is pulled to low state by the device). *****************************************************************************/ uint32_t S2PI_ReadIrqPin(s2pi_slave_t slave); /*!*************************************************************************** - * @brief Cycles the chip select line. + * @brief Cycles the chip select line. * * @details In order to cancel the integration on the ASIC, a fast toggling - * of the chip select pin of the corresponding SPI slave is required. - * Therefore, this function toggles the CS from high to low and back. - * The SPI instance for the specified S2PI slave must be idle, - * otherwise the status #STATUS_BUSY is returned. + * of the chip select pin of the corresponding SPI slave is required. + * Therefore, this function toggles the CS from high to low and back. + * The SPI instance for the specified S2PI slave must be idle, + * otherwise the status #STATUS_BUSY is returned. * - * @param slave The specified S2PI slave. - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param slave The specified S2PI slave. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t S2PI_CycleCsPin(s2pi_slave_t slave); - - /*!***************************************************************************** - * @brief Captures the S2PI pins for GPIO usage. + * @brief Captures the S2PI pins for GPIO usage. * - * @details The SPI is disabled (module status: #STATUS_S2PI_GPIO_MODE) and the - * pins are configured for GPIO operation. The GPIO control must be - * release with the #S2PI_ReleaseGpioControl function in order to - * switch back to ordinary SPI functionality. + * @details The SPI is disabled (module status: #STATUS_S2PI_GPIO_MODE) and the + * pins are configured for GPIO operation. The GPIO control must be + * release with the #S2PI_ReleaseGpioControl function in order to + * switch back to ordinary SPI functionality. * - * @note This function is only called during device initialization! + * @note This function is only called during device initialization! * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param slave The specified S2PI slave. Note that the slave information is + * only required if multiple SPI instances are used in order to + * map to the correct SPI instance. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t S2PI_CaptureGpioControl(void); +status_t S2PI_CaptureGpioControl(s2pi_slave_t slave); /*!***************************************************************************** - * @brief Releases the S2PI pins from GPIO usage and switches back to SPI mode. + * @brief Releases the S2PI pins from GPIO usage and switches back to SPI mode. * - * @details The GPIO pins are configured for SPI operation and the GPIO mode is - * left. Must be called if the pins are captured for GPIO operation via - * the #S2PI_CaptureGpioControl function. + * @details The GPIO pins are configured for SPI operation and the GPIO mode is + * left. Must be called if the pins are captured for GPIO operation via + * the #S2PI_CaptureGpioControl function. * - * @note This function is only called during device initialization! + * @note This function is only called during device initialization! * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param slave The specified S2PI slave. Note that the slave information is + * only required if multiple SPI instances are used in order to + * map to the correct SPI instance. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ -status_t S2PI_ReleaseGpioControl(void); +status_t S2PI_ReleaseGpioControl(s2pi_slave_t slave); /*!***************************************************************************** - * @brief Writes the output for a specified SPI pin in GPIO mode. + * @brief Writes the output for a specified SPI pin in GPIO mode. * * @details This function writes the value of an SPI pin if the SPI pins are - * captured for GPIO operation via the #S2PI_CaptureGpioControl previously. + * captured for GPIO operation via the #S2PI_CaptureGpioControl previously. * - * @note Since some GPIO peripherals switch the GPIO pins very fast a delay - * must be added after each GBIO access (i.e. right before returning - * from the #S2PI_WriteGpioPin method) in order to decrease the baud - * rate of the software EEPROM protocol. Increase the delay if timing - * issues occur while reading the EERPOM. For example: - * Delay = 10 µsec => Baud Rate < 100 kHz + * @note Since some GPIO peripherals switch the GPIO pins very fast a delay + * must be added after each GBIO access (i.e. right before returning + * from the #S2PI_WriteGpioPin method) in order to decrease the baud + * rate of the software EEPROM protocol. Increase the delay if timing + * issues occur while reading the EERPOM. For example: + * Delay = 10 µsec => Baud Rate < 100 kHz * - * @note This function is only called during device initialization! + * @note This function is only called during device initialization! * - * @param slave The specified S2PI slave. - * @param pin The specified S2PI pin. - * @param value The GPIO pin state to write (0 = low, 1 = high). - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param slave The specified S2PI slave. + * @param pin The specified S2PI pin. + * @param value The GPIO pin state to write (0 = low, 1 = high). + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t S2PI_WriteGpioPin(s2pi_slave_t slave, s2pi_pin_t pin, uint32_t value); /*!***************************************************************************** - * @brief Reads the input from a specified SPI pin in GPIO mode. + * @brief Reads the input from a specified SPI pin in GPIO mode. * * @details This function reads the value of an SPI pin if the SPI pins are - * captured for GPIO operation via the #S2PI_CaptureGpioControl previously. + * captured for GPIO operation via the #S2PI_CaptureGpioControl previously. * - * @note This function is only called during device initialization! + * @note This function is only called during device initialization! * - * @param slave The specified S2PI slave. - * @param pin The specified S2PI pin. - * @param value The GPIO pin state to read (0 = low, GND level, 1 = high, VCC level). - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param slave The specified S2PI slave. + * @param pin The specified S2PI pin. + * @param value The GPIO pin state to read (0 = low, GND level, 1 = high, VCC level). + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t S2PI_ReadGpioPin(s2pi_slave_t slave, s2pi_pin_t pin, uint32_t *value); -#ifdef __cplusplus -} -#endif /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif // ARGUS_S2PI_H diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_timer.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_timer.h index 80deb42016..b736010682 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_timer.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_timer.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file provides an interface for the required timer modules. + * @brief This file is part of the AFBR-S50 API. + * @details This file provides an interface for the required timer modules. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,118 +36,117 @@ #ifndef ARGUS_TIMER_H #define ARGUS_TIMER_H - #ifdef __cplusplus extern "C" { #endif /*!*************************************************************************** - * @defgroup argus_timer Timer: Hardware Timer Interface - * @ingroup argus_platform + * @defgroup argus_timer Timer: Hardware Timer Interface + * @ingroup argus_hal * - * @brief Timer implementations for lifetime counting as well as periodic - * callback. + * @brief Timer implementations for lifetime counting as well as periodic + * callback. * - * @details The module provides an interface to the timing utilities that - * are required by the AFBR-S50 time-of-flight sensor API. + * @details The module provides an interface to the timing utilities that + * are required by the AFBR-S50 time-of-flight sensor API. * - * Two essential features have to be provided by the user code: - * 1. Time Measurement Capability: In order to keep track of outgoing - * signals, the API needs to measure elapsed time. In order to - * provide optimum device performance, the granularity should be - * around 10 to 100 microseconds. - * 2. Periodic Callback: The API provides an automatic starting of - * measurement cycles at a fixed frame rate via a periodic - * interrupt timer. If this feature is not used, implementation - * of the periodic interrupts can be skipped. An weak default - * implementation is provide in the API. - * . + * Two essential features have to be provided by the user code: + * 1. Time Measurement Capability: In order to keep track of outgoing + * signals, the API needs to measure elapsed time. In order to + * provide optimum device performance, the granularity should be + * around 10 to 100 microseconds. + * 2. Periodic Callback: The API provides an automatic starting of + * measurement cycles at a fixed frame rate via a periodic + * interrupt timer. If this feature is not used, implementation + * of the periodic interrupts can be skipped. An weak default + * implementation is provide in the API. + * . * - * The time measurement feature is simply implemented by the function - * #Timer_GetCounterValue. Whenever the function is called, the - * provided counter values must be written with the values obtained - * by the current time. + * The time measurement feature is simply implemented by the function + * #Timer_GetCounterValue. Whenever the function is called, the + * provided counter values must be written with the values obtained + * by the current time. * - * The periodic interrupt timer is a simple callback interface. - * After installing the callback function pointer via #Timer_SetCallback, - * the timer can be started by setting interval via #Timer_SetInterval. - * From then, the callback is invoked periodically as the corresponding - * interval may specify. The timer is stopped by setting the interval - * to 0 using the #Timer_SetInterval function. The interval can be - * updated at any time by updating the interval via the #Timer_SetInterval - * function. To any of these functions, an abstract parameter pointer - * must be passed. This parameter is passed back to the callback any - * time it is invoked. + * The periodic interrupt timer is a simple callback interface. + * After installing the callback function pointer via #Timer_SetCallback, + * the timer can be started by setting interval via #Timer_SetInterval. + * From then, the callback is invoked periodically as the corresponding + * interval may specify. The timer is stopped by setting the interval + * to 0 using the #Timer_SetInterval function. The interval can be + * updated at any time by updating the interval via the #Timer_SetInterval + * function. To any of these functions, an abstract parameter pointer + * must be passed. This parameter is passed back to the callback any + * time it is invoked. * - * In order to provide the usage of multiple devices, an mechanism is - * introduced to allow the installation of multiple callback interval - * at the same time. Therefore, the abstract parameter pointer is used - * to identify the corresponding callback interval. For example, there - * are two callbacks for two intervals, t1 and t2, required. The user - * can start two timers by calling the #Timer_SetInterval method twice, - * but with an individual parameter pointer, ptr1 and ptr2, each: - * \code - * Timer_SetInterval(100000, ptr1); // 10 ms callback w/ parameter ptr1 - * Timer_SetInterval(200000, ptr2); // 20 ms callback w/ parameter ptr1 - * \endcode + * In order to provide the usage of multiple devices, an mechanism is + * introduced to allow the installation of multiple callback interval + * at the same time. Therefore, the abstract parameter pointer is used + * to identify the corresponding callback interval. For example, there + * are two callbacks for two intervals, t1 and t2, required. The user + * can start two timers by calling the #Timer_SetInterval method twice, + * but with an individual parameter pointer, ptr1 and ptr2, each: + * \code + * Timer_SetInterval(100000, ptr1); // 10 ms callback w/ parameter ptr1 + * Timer_SetInterval(200000, ptr2); // 20 ms callback w/ parameter ptr1 + * \endcode * - * Note that the implemented timer module must therefore support - * as many different intervals as instances of the AFBR-S50 device are - * used. + * Note that the implemented timer module must therefore support + * as many different intervals as instances of the AFBR-S50 device are + * used. * - * @addtogroup argus_timer + * @addtogroup argus_timer * @{ *****************************************************************************/ -#include "api/argus_def.h" +#include "utility/status.h" /******************************************************************************* * Lifetime Counter Timer Interface ******************************************************************************/ /*!*************************************************************************** - * @brief Obtains the lifetime counter value from the timers. + * @brief Obtains the lifetime counter value from the timers. * * @details The function is required to get the current time relative to any - * point in time, e.g. the startup time. The returned values \p hct and - * \p lct are given in seconds and microseconds respectively. The current - * elapsed time since the reference time is then calculated from: + * point in time, e.g. the startup time. The returned values \p hct and + * \p lct are given in seconds and microseconds respectively. The current + * elapsed time since the reference time is then calculated from: * - * t_now [µsec] = hct * 1000000 µsec + lct * 1 µsec + * t_now [µsec] = hct * 1000000 µsec + lct * 1 µsec * - * Note that the accuracy/granularity of the lifetime counter does - * not need to be 1 µsec. Usually, a granularity of approximately - * 100 µsec is sufficient. However, in case of very high frame rates - * (above 1000 frames per second), it is recommended to implement - * an even lower granularity (somewhere in the 10 µsec regime). + * Note that the accuracy/granularity of the lifetime counter does + * not need to be 1 µsec. Usually, a granularity of approximately + * 100 µsec is sufficient. However, in case of very high frame rates + * (above 1000 frames per second), it is recommended to implement + * an even lower granularity (somewhere in the 10 µsec regime). * - * It must be guaranteed, that each call of the #Timer_GetCounterValue - * function must provide a value that is greater or equal, but never lower, - * than the value returned from the previous call. + * It must be guaranteed, that each call of the #Timer_GetCounterValue + * function must provide a value that is greater or equal, but never lower, + * than the value returned from the previous call. * - * A hardware based implementation of the lifetime counter functionality - * would be to chain two distinct timers such that counter 2 increases - * its value when counter 1 wraps to 0. The easiest way is to setup - * counter 1 to wrap exactly every second. Counter 1 would than count - * the sub-seconds (i.e. µsec) value (\p lct) and counter 2 the seconds - * (\p hct) value. A 16-bit counter is sufficient in case of counter 1 - * while counter 2 must be a 32-bit version. + * A hardware based implementation of the lifetime counter functionality + * would be to chain two distinct timers such that counter 2 increases + * its value when counter 1 wraps to 0. The easiest way is to setup + * counter 1 to wrap exactly every second. Counter 1 would than count + * the sub-seconds (i.e. µsec) value (\p lct) and counter 2 the seconds + * (\p hct) value. A 16-bit counter is sufficient in case of counter 1 + * while counter 2 must be a 32-bit version. * - * In case of a lack of available hardware timers, a software solution - * can be used that requires only a 16-bit timer. In a simple scenario, - * the timer is configured to wrap around every second and increase - * a software counter value in its interrupt service routine (triggered - * with the wrap around event) every time the wrap around occurs. + * In case of a lack of available hardware timers, a software solution + * can be used that requires only a 16-bit timer. In a simple scenario, + * the timer is configured to wrap around every second and increase + * a software counter value in its interrupt service routine (triggered + * with the wrap around event) every time the wrap around occurs. * * - * @note The implementation of this function is mandatory for the correct - * execution of the API. + * @note The implementation of this function is mandatory for the correct + * execution of the API. * - * @param hct A pointer to the high counter value bits representing current - * time in seconds. + * @param hct A pointer to the high counter value bits representing current + * time in seconds. * - * @param lct A pointer to the low counter value bits representing current - * time in microseconds. Range: 0, .., 999999 µsec + * @param lct A pointer to the low counter value bits representing current + * time in microseconds. Range: 0, .., 999999 µsec *****************************************************************************/ void Timer_GetCounterValue(uint32_t *hct, uint32_t *lct); @@ -156,68 +155,70 @@ void Timer_GetCounterValue(uint32_t *hct, uint32_t *lct); ******************************************************************************/ /*!*************************************************************************** - * @brief The callback function type for periodic interrupt timer. + * @brief The callback function type for periodic interrupt timer. * - * @details The function that is invoked every time a specified interval elapses. - * An abstract parameter is passed to the function whenever it is called. + * @details The function that is invoked every time a specified interval elapses. + * An abstract parameter is passed to the function whenever it is called. * - * @param param An abstract parameter to be passed to the callback. This is - * also the identifier of the given interval. + * @param param An abstract parameter to be passed to the callback. This is + * also the identifier of the given interval. *****************************************************************************/ typedef void (*timer_cb_t)(void *param); /*!*************************************************************************** - * @brief Installs an periodic timer callback function. + * @brief Installs an periodic timer callback function. * - * @details Installs an periodic timer callback function that is invoked whenever - * an interval elapses. The callback is the same for any interval, - * however, the single intervals can be identified by the passed - * parameter. - * Passing a zero-pointer removes and disables the callback. + * @details Installs an periodic timer callback function that is invoked whenever + * an interval elapses. The callback is the same for any interval, + * however, the single intervals can be identified by the passed + * parameter. + * Passing a zero-pointer removes and disables the callback. * - * @note The implementation of this function is optional for the correct - * execution of the API. If not implemented, a weak implementation - * within the API will be used that disable the periodic timer callback - * and thus the automatic starting of measurements from the background. + * @note The implementation of this function is optional for the correct + * execution of the API. If not implemented, a weak implementation + * within the API will be used that disable the periodic timer callback + * and thus the automatic starting of measurements from the background. * - * @param f The timer callback function. + * @param f The timer callback function. * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Timer_SetCallback(timer_cb_t f); /*!*************************************************************************** - * @brief Sets the timer interval for a specified callback parameter. + * @brief Sets the timer interval for a specified callback parameter. * - * @details Sets the callback interval for the specified parameter and starts - * the timer with a new interval. If there is already an interval with - * the given parameter, the timer is restarted with the given interval. - * If the same time interval as already set is passed, nothing happens. - * Passing a interval of 0 disables the timer. + * @details Sets the callback interval for the specified parameter and starts + * the timer with a new interval. If there is already an interval with + * the given parameter, the timer is restarted with the given interval. + * If the same time interval as already set is passed, nothing happens. + * Passing a interval of 0 disables the timer. * - * Note that a microsecond granularity for the timer interrupt period is - * not required. Usually a microseconds granularity is sufficient. - * The required granularity depends on the targeted frame rate, e.g. in - * case of more than 1 kHz measurement rate, a granularity of less than - * a microsecond is required to achieve the given frame rate. + * When enabling the timer (or resetting by applying another interval), + * the first timer interrupt must happen after the specified interval. * - * @note The implementation of this function is optional for the correct - * execution of the API. If not implemented, a weak implementation - * within the API will be used that disable the periodic timer callback - * and thus the automatic starting of measurements from the background. + * Note that a microsecond granularity for the timer interrupt period is + * not required. Usually a milliseconds granularity is sufficient. + * The required granularity depends on the targeted frame rate, e.g. in + * case of more than 1 kHz measurement rate, a granularity of less than + * a millisecond is required to achieve the given frame rate. * - * @param dt_microseconds The callback interval in microseconds. + * @note The implementation of this function is optional for the correct + * execution of the API. If not implemented, a weak implementation + * within the API will be used that disable the periodic timer callback + * and thus the automatic starting of measurements from the background. * - * @param param An abstract parameter to be passed to the callback. This is - * also the identifier of the given interval. + * @param dt_microseconds The callback interval in microseconds. * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * @param param An abstract parameter to be passed to the callback. This is + * also the identifier of the given interval. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Timer_SetInterval(uint32_t dt_microseconds, void *param); -#ifdef __cplusplus -} -#endif - /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* ARGUS_TIMER_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_def.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_def.h index ae7422ada5..8d5406360b 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_def.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_def.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details Provides definitions and basic macros for fixed point data types. + * @brief This file is part of the AFBR-S50 API. + * @details Provides definitions and basic macros for fixed point data types. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,23 +36,30 @@ #ifndef FP_DEF_H #define FP_DEF_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup fixedpoint Fixed Point Math - * @ingroup argusutil - * @brief A basic math library for fixed point number in the Qx.y fomat. - * @details This module contains common fixed point type definitions as - * well as some basic math algorithms. All types are based on - * integer types. The number are defined with the Q number format. + * @defgroup argus_fp Fixed Point Math + * @ingroup argus_util * - * - For a description of the Q number format refer to: - * https://en.wikipedia.org/wiki/Q_(number_format) - * - Another resource for fixed point math in C might be found at - * http://www.eetimes.com/author.asp?section_id=36&doc_id=1287491 - * . - * @warning This definitions are not portable and work only with - * little-endian systems! - * @addtogroup fixedpoint + * @brief A basic math library for fixed point number in the Qx.y fomat. + * + * @details This module contains common fixed point type definitions as + * well as some basic math algorithms. All types are based on + * integer types. The number are defined with the Q number format. + * + * - For a description of the Q number format refer to: + * https://en.wikipedia.org/wiki/Q_(number_format) + * - Another resource for fixed point math in C might be found at + * http://www.eetimes.com/author.asp?section_id=36&doc_id=1287491 + * . + * + * @warning This definitions are not portable and work only with + * little-endian systems! + * + * @addtogroup argus_fp * @{ *****************************************************************************/ @@ -66,11 +73,11 @@ ***** UQ6.2 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ6.2 - * @details An unsigned fixed point number format based on the 8-bit unsigned - * integer type with 6 integer and 2 fractional bits. - * - Range: 0 .. 63.75 - * - Granularity: 0.25 + * @brief Unsigned fixed point number: UQ6.2 + * @details An unsigned fixed point number format based on the 8-bit unsigned + * integer type with 6 integer and 2 fractional bits. + * - Range: 0 .. 63.75 + * - Granularity: 0.25 *****************************************************************************/ typedef uint8_t uq6_2_t; @@ -86,11 +93,11 @@ typedef uint8_t uq6_2_t; ***** UQ4.4 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ4.4 - * @details An unsigned fixed point number format based on the 8-bit unsigned - * integer type with 4 integer and 4 fractional bits. - * - Range: 0 .. 15.9375 - * - Granularity: 0.0625 + * @brief Unsigned fixed point number: UQ4.4 + * @details An unsigned fixed point number format based on the 8-bit unsigned + * integer type with 4 integer and 4 fractional bits. + * - Range: 0 .. 15.9375 + * - Granularity: 0.0625 *****************************************************************************/ typedef uint8_t uq4_4_t; @@ -106,11 +113,11 @@ typedef uint8_t uq4_4_t; ***** UQ2.6 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ2.6 - * @details An unsigned fixed point number format based on the 8-bit unsigned - * integer type with 2 integer and 6 fractional bits. - * - Range: 0 .. 3.984375 - * - Granularity: 0.015625 + * @brief Unsigned fixed point number: UQ2.6 + * @details An unsigned fixed point number format based on the 8-bit unsigned + * integer type with 2 integer and 6 fractional bits. + * - Range: 0 .. 3.984375 + * - Granularity: 0.015625 *****************************************************************************/ typedef uint8_t uq2_6_t; @@ -126,11 +133,11 @@ typedef uint8_t uq2_6_t; ***** UQ1.7 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ1.7 - * @details An unsigned fixed point number format based on the 8-bit unsigned - * integer type with 1 integer and 7 fractional bits. - * - Range: 0 .. 1.9921875 - * - Granularity: 0.0078125 + * @brief Unsigned fixed point number: UQ1.7 + * @details An unsigned fixed point number format based on the 8-bit unsigned + * integer type with 1 integer and 7 fractional bits. + * - Range: 0 .. 1.9921875 + * - Granularity: 0.0078125 *****************************************************************************/ typedef uint8_t uq1_7_t; @@ -146,11 +153,11 @@ typedef uint8_t uq1_7_t; ***** UQ0.8 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ0.8 - * @details An unsigned fixed point number format based on the 8-bit unsigned - * integer type with 1 integer and 7 fractional bits. - * - Range: 0 .. 0.99609375 - * - Granularity: 0.00390625 + * @brief Unsigned fixed point number: UQ0.8 + * @details An unsigned fixed point number format based on the 8-bit unsigned + * integer type with 1 integer and 7 fractional bits. + * - Range: 0 .. 0.99609375 + * - Granularity: 0.00390625 *****************************************************************************/ typedef uint8_t uq0_8_t; @@ -167,11 +174,11 @@ typedef uint8_t uq0_8_t; ***** Q3.4 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q3.4 - * @details A signed fixed point number format based on the 8-bit signed - * integer type with 3 integer and 4 fractional bits. - * - Range: -8 ... 7.9375 - * - Granularity: 0.0625 + * @brief Signed fixed point number: Q3.4 + * @details A signed fixed point number format based on the 8-bit signed + * integer type with 3 integer and 4 fractional bits. + * - Range: -8 ... 7.9375 + * - Granularity: 0.0625 *****************************************************************************/ typedef int8_t q3_4_t; @@ -189,11 +196,11 @@ typedef int8_t q3_4_t; ***** Q1.6 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q1.6 - * @details A signed fixed point number format based on the 8-bit signed - * integer type with 1 integer and 6 fractional bits. - * - Range: -2 ... 1.984375 - * - Granularity: 0.015625 + * @brief Signed fixed point number: Q1.6 + * @details A signed fixed point number format based on the 8-bit signed + * integer type with 1 integer and 6 fractional bits. + * - Range: -2 ... 1.984375 + * - Granularity: 0.015625 *****************************************************************************/ typedef int8_t q1_6_t; @@ -215,11 +222,11 @@ typedef int8_t q1_6_t; ***** UQ12.4 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ12.4 - * @details An unsigned fixed point number format based on the 16-bit unsigned - * integer type with 12 integer and 4 fractional bits. - * - Range: 0 ... 4095.9375 - * - Granularity: 0.0625 + * @brief Unsigned fixed point number: UQ12.4 + * @details An unsigned fixed point number format based on the 16-bit unsigned + * integer type with 12 integer and 4 fractional bits. + * - Range: 0 ... 4095.9375 + * - Granularity: 0.0625 *****************************************************************************/ typedef uint16_t uq12_4_t; @@ -235,11 +242,11 @@ typedef uint16_t uq12_4_t; ***** UQ10.6 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ10.6 - * @details An unsigned fixed point number format based on the 16-bit unsigned - * integer type with 10 integer and 6 fractional bits. - * - Range: 0 ... 1023.984375 - * - Granularity: 0.015625 + * @brief Unsigned fixed point number: UQ10.6 + * @details An unsigned fixed point number format based on the 16-bit unsigned + * integer type with 10 integer and 6 fractional bits. + * - Range: 0 ... 1023.984375 + * - Granularity: 0.015625 *****************************************************************************/ typedef uint16_t uq10_6_t; @@ -255,11 +262,11 @@ typedef uint16_t uq10_6_t; ***** UQ1.15 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ1.15 - * @details An unsigned fixed point number format based on the 16-bit unsigned - * integer type with 1 integer and 15 fractional bits. - * - Range: 0 .. 1.999969 - * - Granularity: 0.000031 + * @brief Unsigned fixed point number: UQ1.15 + * @details An unsigned fixed point number format based on the 16-bit unsigned + * integer type with 1 integer and 15 fractional bits. + * - Range: 0 .. 1.999969 + * - Granularity: 0.000031 *****************************************************************************/ typedef uint16_t uq1_15_t; @@ -275,11 +282,11 @@ typedef uint16_t uq1_15_t; ***** UQ0.16 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ0.16 - * @details An unsigned fixed point number format based on the 16-bit unsigned - * integer type with 0 integer and 16 fractional bits. - * - Range: 0 .. 0.9999847412109375 - * - Granularity: 1.52587890625e-5 + * @brief Unsigned fixed point number: UQ0.16 + * @details An unsigned fixed point number format based on the 16-bit unsigned + * integer type with 0 integer and 16 fractional bits. + * - Range: 0 .. 0.9999847412109375 + * - Granularity: 1.52587890625e-5 *****************************************************************************/ typedef uint16_t uq0_16_t; @@ -296,11 +303,11 @@ typedef uint16_t uq0_16_t; ***** Q11.4 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q11.4 - * @details A signed fixed point number format based on the 16-bit signed - * integer type with 11 integer and 4 fractional bits. - * - Range: -2048 ... 2047.9375 - * - Granularity: 0.0625 + * @brief Signed fixed point number: Q11.4 + * @details A signed fixed point number format based on the 16-bit signed + * integer type with 11 integer and 4 fractional bits. + * - Range: -2048 ... 2047.9375 + * - Granularity: 0.0625 *****************************************************************************/ typedef int16_t q11_4_t; @@ -319,11 +326,11 @@ typedef int16_t q11_4_t; ***** Q7.8 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q7.8 - * @details A signed fixed point number format based on the 16-bit signed - * integer type with 7 integer and 8 fractional bits. - * - Range: -128 .. 127.99609375 - * - Granularity: 0.00390625 + * @brief Signed fixed point number: Q7.8 + * @details A signed fixed point number format based on the 16-bit signed + * integer type with 7 integer and 8 fractional bits. + * - Range: -128 .. 127.99609375 + * - Granularity: 0.00390625 *****************************************************************************/ typedef int16_t q7_8_t; @@ -342,11 +349,11 @@ typedef int16_t q7_8_t; ***** Q3.12 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q3.12 - * @details A signed fixed point number format based on the 16-bit integer - * type with 3 integer and 12 fractional bits. - * - Range: -8 .. 7.99975586 - * - Granularity: 0.00024414 + * @brief Signed fixed point number: Q3.12 + * @details A signed fixed point number format based on the 16-bit integer + * type with 3 integer and 12 fractional bits. + * - Range: -8 .. 7.99975586 + * - Granularity: 0.00024414 *****************************************************************************/ typedef int16_t q3_12_t; @@ -365,11 +372,11 @@ typedef int16_t q3_12_t; ***** Q0.15 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q0.15 - * @details A signed fixed point number format based on the 16-bit integer - * type with 0 integer and 15 fractional bits. - * - Range: -1 .. 0.999969482 - * - Granularity: 0.000030518 + * @brief Signed fixed point number: Q0.15 + * @details A signed fixed point number format based on the 16-bit integer + * type with 0 integer and 15 fractional bits. + * - Range: -1 .. 0.999969482 + * - Granularity: 0.000030518 *****************************************************************************/ typedef int16_t q0_15_t; @@ -389,11 +396,11 @@ typedef int16_t q0_15_t; ***** UQ28.4 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ28.4 - * @details An unsigned fixed point number format based on the 32-bit unsigned - * integer type with 28 integer and 4 fractional bits. - * - Range: 0 ... 268435455.9375 - * - Granularity: 0.0625 + * @brief Unsigned fixed point number: UQ28.4 + * @details An unsigned fixed point number format based on the 32-bit unsigned + * integer type with 28 integer and 4 fractional bits. + * - Range: 0 ... 268435455.9375 + * - Granularity: 0.0625 *****************************************************************************/ typedef uint32_t uq28_4_t; @@ -409,11 +416,11 @@ typedef uint32_t uq28_4_t; ***** UQ16.16 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ16.16 - * @details An unsigned fixed point number format based on the 32-bit unsigned - * integer type with 16 integer and 16 fractional bits. - * - Range: 0 ... 65535.999984741 - * - Granularity: 0.000015259 + * @brief Unsigned fixed point number: UQ16.16 + * @details An unsigned fixed point number format based on the 32-bit unsigned + * integer type with 16 integer and 16 fractional bits. + * - Range: 0 ... 65535.999984741 + * - Granularity: 0.000015259 *****************************************************************************/ typedef uint32_t uq16_16_t; @@ -432,11 +439,11 @@ typedef uint32_t uq16_16_t; ***** UQ10.22 ******************************************************************************/ /*!*************************************************************************** - * @brief Unsigned fixed point number: UQ10.22 - * @details An unsigned fixed point number format based on the 32-bit unsigned - * integer type with 10 integer and 22 fractional bits. - * - Range: 0 ... 1023.99999976158 - * - Granularity: 2.38418579101562E-07 + * @brief Unsigned fixed point number: UQ10.22 + * @details An unsigned fixed point number format based on the 32-bit unsigned + * integer type with 10 integer and 22 fractional bits. + * - Range: 0 ... 1023.99999976158 + * - Granularity: 2.38418579101562E-07 *****************************************************************************/ typedef uint32_t uq10_22_t; @@ -456,11 +463,11 @@ typedef uint32_t uq10_22_t; ***** Q27.4 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q27.4 - * @details A signed fixed point number format based on the 32-bit signed - * integer type with 27 integer and 4 fractional bits. - * - Range: -134217728 ... 134217727.9375 - * - Granularity: 0.0625 + * @brief Signed fixed point number: Q27.4 + * @details A signed fixed point number format based on the 32-bit signed + * integer type with 27 integer and 4 fractional bits. + * - Range: -134217728 ... 134217727.9375 + * - Granularity: 0.0625 *****************************************************************************/ typedef int32_t q27_4_t; @@ -475,15 +482,35 @@ typedef int32_t q27_4_t; +/******************************************************************************* + ***** Q16.15 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q16.15 + * @details A signed fixed point number format based on the 32-bit integer + * type with 16 integer and 15 fractional bits. + * - Range: -65536 .. 65536.999969482 + * - Granularity: 0.000030518 + *****************************************************************************/ +typedef int32_t q16_15_t; + +/*! Minimum value of Q16.15 number format. */ +#define Q16_15_MIN ((q16_15_t)INT32_MIN) + +/*! Maximum value of Q16.15 number format. */ +#define Q16_15_MAX ((q16_15_t)INT32_MAX) + + + /******************************************************************************* ***** Q15.16 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q15.16 - * @details A signed fixed point number format based on the 32-bit integer - * type with 15 integer and 16 fractional bits. - * - Range: -32768 .. 32767.99998 - * - Granularity: 1.52588E-05 + * @brief Signed fixed point number: Q15.16 + * @details A signed fixed point number format based on the 32-bit integer + * type with 15 integer and 16 fractional bits. + * - Range: -32768 .. 32767.99998 + * - Granularity: 1.52588E-05 *****************************************************************************/ typedef int32_t q15_16_t; @@ -502,11 +529,11 @@ typedef int32_t q15_16_t; ***** Q9.22 ******************************************************************************/ /*!*************************************************************************** - * @brief Signed fixed point number: Q9.22 - * @details A signed fixed point number format based on the 32-bit integer - * type with 9 integer and 22 fractional bits. - * - Range: -512 ... 511.9999998 - * - Granularity: 2.38418579101562E-07 + * @brief Signed fixed point number: Q9.22 + * @details A signed fixed point number format based on the 32-bit integer + * type with 9 integer and 22 fractional bits. + * - Range: -512 ... 511.9999998 + * - Granularity: 2.38418579101562E-07 *****************************************************************************/ typedef int32_t q9_22_t; @@ -522,4 +549,7 @@ typedef int32_t q9_22_t; /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* FP_DEF_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_div.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_div.h new file mode 100644 index 0000000000..60b75a164a --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_div.h @@ -0,0 +1,173 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details Provides definitions and basic macros for fixed point data types. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef FP_DIV_H +#define FP_DIV_H +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @addtogroup argus_fp + * @{ + *****************************************************************************/ + +#include "fp_def.h" +#include "int_math.h" + +/*!*************************************************************************** + * Set to use hardware division (Cortex-M3/4) over software division (Cortex-M0/1). + *****************************************************************************/ +#ifndef USE_HW_DIV +#define USE_HW_DIV 0 +#endif + +/*!*************************************************************************** + * @brief 32-bit implementation of an Q15.16 division. + * + * @details Algorithm to evaluate a/b, where b is in Q15.16 format, on a 32-bit + * architecture with maximum precision. + * The result is correctly rounded and given as the input format. + * Division by 0 yields max. values determined by signa of numerator. + * Too high/low results are truncated to max/min values. + * + * Depending on the architecture, the division is implemented with a 64-bit + * division and shifting (Cortex-M3/4) or as a fast software algorithm + * (Cortex-M0/1) wich runs fast on processors without hardware division. + * + * @see https://code.google.com/archive/p/libfixmath + * + * @param a Numerator in any Qx.y format + * @param b Denominator in Q15.16 format + * @return Result = a/b in the same Qx.y format as the input parameter a. + *****************************************************************************/ +inline int32_t fp_div16(int32_t a, q15_16_t b) +{ + //assert(b); + if (b == 0) { return a < 0 ? INT32_MIN : INT32_MAX; } + +#if USE_HW_DIV + // Tested on Cortex-M4, it takes approx. 75% of the + // software algorithm below. + int64_t c = ((int64_t) a) << 30U; + + if ((uint32_t)(a ^ b) & 0x80000000U) { + c = (((-c) / b) + (1 << 13U)) >> 14U; + + if (c > 0x80000000U) { return INT32_MIN; } + + return -c; + + } else { + c = ((c / b) + (1 << 13U)) >> 14U; + + if (c > (int64_t)INT32_MAX) { return INT32_MAX; } + + return c; + } + +#else + // This uses the basic binary restoring division algorithm. + // It appears to be faster to do the whole division manually than + // trying to compose a 64-bit divide out of 32-bit divisions on + // platforms without hardware divide. + // Tested on Cortex-M0, it takes approx. 33% of the time of the + // 64-bit version above. + + uint32_t remainder = absval(a); + uint32_t divider = absval(b); + + uint32_t quotient = 0; + uint32_t bit = 0x10000U; + + /* The algorithm requires D >= R */ + while (divider < remainder) { + divider <<= 1U; + bit <<= 1U; + } + + if (!bit) { + if ((uint32_t)(a ^ b) & 0x80000000U) { // return truncated values + return INT32_MIN; + + } else { + return INT32_MAX; + } + } + + if (divider & 0x80000000U) { + // Perform one step manually to avoid overflows later. + // We know that divider's bottom bit is 0 here. + if (remainder >= divider) { + quotient |= bit; + remainder -= divider; + } + + divider >>= 1U; + bit >>= 1U; + } + + /* Main division loop */ + while (bit && remainder) { + if (remainder >= divider) { + quotient |= bit; + remainder -= divider; + } + + remainder <<= 1U; + bit >>= 1U; + } + + if (remainder >= divider) { + quotient++; + } + + uint32_t result = quotient; + + /* Figure out the sign of result */ + if ((uint32_t)(a ^ b) & 0x80000000U) { + result = -result; + } + + return (int32_t)result; +#endif +} + +/*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* FP_DIV_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_ema.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_ema.h new file mode 100644 index 0000000000..f8c494a630 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_ema.h @@ -0,0 +1,204 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details Provides averaging algorithms for fixed point data types. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef FP_EMA_H +#define FP_EMA_H +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @addtogroup argus_fp + * @{ + *****************************************************************************/ + +#include "fp_def.h" + +#include "utility/fp_rnd.h" +#include "utility/fp_mul.h" + +/*!*************************************************************************** + * @brief Circular exponentially weighted moving average using UQ1.15 format. + * + * @details Evaluates the moving average (exponentially weighted) for circular + * data in UQ1.15 format. + * Circular data is that MAX_VALUE + 1 == MIN_VALUE. For example the + * usual phase information. + * + * Problem: Due to circularity of phase values, i.e. 0+x and 2PI+x are + * the same, the usual EMA has issues with the wrap around effect. + * Especially for vectors with phase around 0 (or 2PI), two values + * like 0 + x and PI - y are averaged to something around PI instead + * of 0 which would be more correct. + * + * Solution: Assume that phase jumps of more than PI are not allowed + * or possible. If a deviation of the new value to the smoothed signal + * occurs, it is clear that this stems from the wrap around effect and + * can be caught and correctly handled by the smoothing algorithm. + * + * Caution: If a target comes immediately into the field of view, phase + * jumps of > PI are indeed possible and volitional. However, the + * averaging break there anyway since the smoothed signal approaches + * only with delay to the correct values. The error made here is, that + * the smoothed signal approaches from the opposite direction. However, + * is approaches even faster since it always takes the shortest + * direction. + * + * @param mean The previous mean value in UQ1.15 format. + * @param x The current value to be added to the average UQ1.15 format. + * @param weight The EMA weight in UQ0.7 format. + * @return The new mean value in UQ1.15 format. + *****************************************************************************/ +inline uq1_15_t fp_ema15c(uq1_15_t mean, uq1_15_t x, uq0_8_t weight) +{ + if (weight == 0) { return x; } + + // Heeds the wrap around effect by casting dx to int16: + const int16_t dx = (int16_t)(x - mean); + const int32_t diff = weight * dx; + return (uq1_15_t)fp_rnds((mean << 8U) + diff, 8U); +} + +/*!*************************************************************************** + * @brief Exponentially weighted moving average using the Q11.4 format. + * + * @details Evaluates the moving average (exponentially weighted) for data in + * Q11.4 format. + * + * @param mean The previous mean value in Q11.4 format. + * @param x The current value to be added to the average Q11.4 format. + * @param weight The EMA weight in UQ0.7 format. + * @return The new mean value in Q11.4 format. + *****************************************************************************/ +inline q11_4_t fp_ema4(q11_4_t mean, q11_4_t x, uq0_8_t weight) +{ + if (weight == 0) { return x; } + + const int32_t dx = x - mean; + const int32_t diff = weight * dx; + return (q11_4_t)fp_rnds((mean << 8U) + diff, 8U); +} + +/*!*************************************************************************** + * @brief Exponentially weighted moving average using the Q7.8 format. + * + * @details Evaluates the moving average (exponentially weighted) for data in + * Q7.8 format. + * + * @param mean The previous mean value in Q7.8 format. + * @param x The current value to be added to the average Q7.8 format. + * @param weight The EMA weight in UQ0.7 format. + * @return The new mean value in Q7.8 format. + *****************************************************************************/ +inline q7_8_t fp_ema8(q7_8_t mean, q7_8_t x, uq0_8_t weight) +{ + return (q7_8_t)fp_ema4(mean, x, weight); +} + +/*!*************************************************************************** + * @brief Exponentially weighted moving average using the Q15.16 format. + * + * @details Evaluates the moving average (exponentially weighted) for data in + * Q15.16 format. + * + * @param mean The previous mean value in Q15.16 format. + * @param x The current value to be added to the average Q15.16 format. + * @param weight The EMA weight in UQ0.7 format. + * @return The new mean value in Q15.16 format. + *****************************************************************************/ +inline uint32_t uint_ema32(uint32_t mean, uint32_t x, uq0_8_t weight) +{ + if (weight == 0) { return x; } + + if (x > mean) { + const uint32_t dx = x - mean; + const uint32_t diff = fp_mulu(weight, dx, 8U); + return mean + diff; + + } else { + const uint32_t dx = mean - x; + const uint32_t diff = fp_mulu(weight, dx, 8U); + return mean - diff; + } +} +/*!*************************************************************************** + * @brief Exponentially weighted moving average using the Q15.16 format. + * + * @details Evaluates the moving average (exponentially weighted) for data in + * Q15.16 format. + * + * @param mean The previous mean value in Q15.16 format. + * @param x The current value to be added to the average Q15.16 format. + * @param weight The EMA weight in UQ0.7 format. + * @return The new mean value in Q15.16 format. + *****************************************************************************/ +inline int32_t int_ema32(int32_t mean, int32_t x, uq0_8_t weight) +{ + if (weight == 0) { return x; } + + if (x > mean) { + const uint32_t dx = x - mean; + const uint32_t diff = fp_mulu(weight, dx, 8U); + return mean + diff; + + } else { + const uint32_t dx = mean - x; + const uint32_t diff = fp_mulu(weight, dx, 8U); + return mean - diff; + } +} + +/*!*************************************************************************** + * @brief Exponentially weighted moving average using the Q15.16 format. + * + * @details Evaluates the moving average (exponentially weighted) for data in + * Q15.16 format. + * + * @param mean The previous mean value in Q15.16 format. + * @param x The current value to be added to the average Q15.16 format. + * @param weight The EMA weight in UQ0.7 format. + * @return The new mean value in Q15.16 format. + *****************************************************************************/ +inline q15_16_t fp_ema16(q15_16_t mean, q15_16_t x, uq0_8_t weight) +{ + return (q15_16_t)int_ema32(mean, x, weight); +} + +/*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* FP_EMA_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_exp.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_exp.h new file mode 100644 index 0000000000..f845ca6e00 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_exp.h @@ -0,0 +1,69 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details This file provides an exponential function for fixed point type. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef FP_EXP_H +#define FP_EXP_H +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @addtogroup argus_fp + * @{ + *****************************************************************************/ + +#include "fp_def.h" + +/*!*************************************************************************** + * @brief Calculates the exponential of an fixed point number Q15.16 format. + * + * @details Calculates y = exp(x) in fixed point representation. + * + * Note that the result might not be 100 % accurate and might contain + * a small error! + * + * @see https://www.quinapalus.com/efunc.html + * + * @param x The input parameter in unsigned fixed point format Q15.16. + * @return Result y = exp(x) in the UQ16.16 format. + *****************************************************************************/ +uq16_16_t fp_exp16(q15_16_t x); + +/*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* FP_DIV_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_log.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_log.h new file mode 100644 index 0000000000..6bc42b2ec7 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_log.h @@ -0,0 +1,69 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details This file provides an logarithm function for fixed point type. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef FP_LOG_H +#define FP_LOG_H +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @addtogroup argus_fp + * @{ + *****************************************************************************/ + +#include "fp_def.h" + +/*!*************************************************************************** + * @brief Calculates the natural logarithm (base e) of an fixed point number. + * + * @details Calculates y = ln(x) = log_e(x) in fixed point representation. + * + * Note that the result might not be 100 % accurate and might contain + * a small error! + * + * @see https://www.quinapalus.com/efunc.html + * + * @param x The input parameter in unsigned fixed point format Q15.16. + * @return Result y = ln(x) in the UQ16.16 format. + *****************************************************************************/ +q15_16_t fp_log16(uq16_16_t x); + +/*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* FP_DIV_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_mul.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_mul.h new file mode 100644 index 0000000000..78db582644 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_mul.h @@ -0,0 +1,235 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details Provides definitions and basic macros for fixed point data types. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef FP_MUL_H +#define FP_MUL_H +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @addtogroup argus_fp + * @{ + *****************************************************************************/ + +#include "fp_def.h" +#include "utility/fp_rnd.h" + +/*!*************************************************************************** + * Set to use hardware division (Cortex-M3/4) over software division (Cortex-M0/1). + *****************************************************************************/ +#ifndef USE_64BIT_MUL +#define USE_64BIT_MUL 0 +#endif + +#if !USE_64BIT_MUL +/*!*************************************************************************** + * @brief Long multiplication of two unsigned 32-bit into an 64-bit value on + * 32-bit architecture. + * + * @details w (two words) gets the product of u and v (one word each). + * w[0] is the most significant word of the result, w[1] the least. + * (The words are in big-endian order). + * It is Knuth's Algorithm M from [Knu2] section 4.3.1. + * * + * @see http://www.hackersdelight.org/hdcodetxt/muldwu.c.txt + * + * @param w The result (u * v) value given as two unsigned 32-bit numbers: + * w[0] is the most significant word of the result, w[1] the least. + * (The words are in big-endian order). + * @param u Left hand side of the multiplication. + * @param v Right hand side of the multiplication. + *****************************************************************************/ +inline void muldwu(uint32_t w[], uint32_t u, uint32_t v) +{ + const uint32_t u0 = u >> 16U; + const uint32_t u1 = u & 0xFFFFU; + const uint32_t v0 = v >> 16U; + const uint32_t v1 = v & 0xFFFFU; + + uint32_t t = u1 * v1; + const uint32_t w3 = t & 0xFFFFU; + uint32_t k = t >> 16U; + + t = u0 * v1 + k; + const uint32_t w2 = t & 0xFFFFU; + const uint32_t w1 = t >> 16U; + + t = u1 * v0 + w2; + k = t >> 16U; + + w[0] = u0 * v0 + w1 + k; + w[1] = (t << 16U) + w3; +} +#endif + +/*!*************************************************************************** + * @brief 64-bit implementation of an unsigned multiplication with fixed point format. + * + * @details Algorithm to evaluate a*b, where a and b are arbitrary fixed point + * number of 32-bit width. The multiplication is done in 64-bit and + * the result is shifted down by the passed shift parameter in order + * to return a 32-bit value. + * The shift is executed with correct rounding. + * + * Note that the result must fit into the 32-bit value. An assertion + * error occurs otherwise (or undefined behavior of no assert available). + * + * @param u The left parameter in UQx1.y1 format + * @param v The right parameter in UQx2.y2 format + * @param shift The final right shift (rounding) value. + * @return Result = (a*b)>>shift in UQx.(y1+y2-shift) format. + *****************************************************************************/ +inline uint32_t fp_mulu(uint32_t u, uint32_t v, uint_fast8_t shift) +{ + assert(shift <= 32); +#if USE_64BIT_MUL + const uint64_t w = (uint64_t)u * (uint64_t)v; + return (w >> shift) + ((w >> (shift - 1)) & 1U); +#else + uint32_t tmp[2] = { 0 }; + muldwu(tmp, u, v); + + assert(shift ? tmp[0] <= (UINT32_MAX >> (32 - shift)) : tmp[0] == 0); + + if (32 - shift) { + return ((tmp[0] << (32 - shift)) + fp_rndu(tmp[1], shift)); + + } else { + return tmp[1] > (UINT32_MAX >> 1) ? tmp[0] + 1 : tmp[0]; + } + +#endif +} + +/*!*************************************************************************** + * @brief 64-bit implementation of a signed multiplication with fixed point format. + * + * @details Algorithm to evaluate a*b, where a and b are arbitrary fixed point + * number of 32-bit width. The multiplication is done in 64-bit and + * the result is shifted down by the passed shift parameter in order + * to return a 32-bit value. + * The shift is executed with correct rounding. + * + * Note that the result must fit into the 32-bit value. An assertion + * error occurs otherwise (or undefined behavior of no assert available). + * + * @param u The left parameter in Qx1.y1 format + * @param v The right parameter in Qx2.y2 format + * @param shift The final right shift (rounding) value. + * @return Result = (a*b)>>shift in Qx.(y1+y2-shift) format. + *****************************************************************************/ +inline int32_t fp_muls(int32_t u, int32_t v, uint_fast8_t shift) +{ + int_fast8_t sign = 1; + + uint32_t u2, v2; + + if (u < 0) { u2 = -u; sign = -sign; } else { u2 = u; } + + if (v < 0) { v2 = -v; sign = -sign; } else { v2 = v; } + + const uint32_t res = fp_mulu(u2, v2, shift); + + assert(sign > 0 ? res <= 0x7FFFFFFFU : res <= 0x80000000U); + + return sign > 0 ? res : -res; +} + + +/*!*************************************************************************** + * @brief 48-bit implementation of a unsigned multiplication with fixed point format. + * + * @details Algorithm to evaluate a*b, where a and b are arbitrary fixed point + * numbers with 32-bit unsigned and 16-bit unsigned format respectively. + * The multiplication is done in two 16x16-bit operations and the + * result is shifted down by the passed shift parameter in order to + * return a 32-bit value. + * + * Note that the result must fit into the 32-bit value. An assertion + * error occurs otherwise (or undefined behavior of no assert available). + * + * @param u The left parameter in Qx1.y1 format + * @param v The right parameter in Qx2.y2 format + * @param shift The final right shift (rounding) value. + * @return Result = (a*b)>>shift in Qx.(y1+y2-shift) format. + *****************************************************************************/ +inline uint32_t fp_mul_u32_u16(uint32_t u, uint16_t v, uint_fast8_t shift) +{ + assert(shift <= 48); + + if (shift > 16) { + uint32_t msk = 0xFFFFU; + uint32_t a = (u >> 16U) * v; + uint32_t b = (msk & u) * v; + return fp_rndu(a, shift - 16) + fp_rndu(b, shift); + + } else { + uint32_t msk = ~(0xFFFFFFFFU << shift); + uint32_t a = (u >> shift) * v; + uint32_t b = fp_rndu((msk & u) * v, shift); + return a + b; + } +} + +/*!*************************************************************************** + * @brief 48-bit implementation of an unsigned/signed multiplication with fixed point format. + * + * @details Algorithm to evaluate a*b, where a and b are arbitrary fixed point + * numbers with 32-bit signed and 16-bit unsigned format respectively. + * The multiplication is done in two 16x16-bit operations and the + * result is shifted down by the passed shift parameter in order to + * return a 32-bit value. + * The shift is executed with correct rounding. + * + * Note that the result must fit into the 32-bit value. An assertion + * error occurs otherwise (or undefined behavior of no assert available). + * + * @param u The left parameter in Qx1.y1 format + * @param v The right parameter in Qx2.y2 format + * @param shift The final right shift (rounding) value. + * @return Result = (a*b)>>shift in Qx.(y1+y2-shift) format. + *****************************************************************************/ +inline int32_t fp_mul_s32_u16(int32_t u, uint16_t v, uint_fast8_t shift) +{ + return u >= 0 ? fp_mul_u32_u16(u, v, shift) : - fp_mul_u32_u16(-u, v, shift); +} + +/*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* FP_MUL_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_rnd.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_rnd.h new file mode 100644 index 0000000000..ad6f71e09c --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_rnd.h @@ -0,0 +1,118 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details Provides definitions and basic macros for fixed point data types. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef FP_RND_H +#define FP_RND_H +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @addtogroup argus_fp + * @{ + *****************************************************************************/ + +#include "fp_def.h" +#include + +/*!*************************************************************************** + * @brief Converting with rounding from UQx.n1 to UQx.n2. + * @details Equivalent to dividing by 2^n with correct rounding to unsigned + * integer values. + * @param Q The number in (U)Qx.n1 fixed point format to be rounded. + * @param n The number of bits to be rounded, + * e.g. UQ8.8 -> UQ12.4 => n = 8 - 4 = 4. + * @return The rounded value in (U)Qx.n2 format. + *****************************************************************************/ +inline uint32_t fp_rndu(uint32_t Q, uint_fast8_t n) +{ + if (n == 0) { return Q; } + + else if (n > 32U) { return 0; } + + // Shift by n>=32 yields undefined behavior! Thus, this extra first + // step is essential to prevent issues. + Q >>= n - 1; + return (Q >> 1) + (Q & 1U); +} + +/*!*************************************************************************** + * @brief Converting with rounding from Qx.n1 to Qx.n2. + * @details Equivalent to dividing by 2^n with correct rounding to integer + * values. + * @param Q The number in (U)Qx.n1 fixed point format to be rounded. + * @param n The number of bits to be rounded, + * e.g. Q7.8 -> Q11.4 => n = 8 - 4 = 4. + * @return The rounded value in (U)Qx.n2 format. + *****************************************************************************/ +inline int32_t fp_rnds(int32_t Q, uint_fast8_t n) +{ + return (Q < 0) ? -fp_rndu(-Q, n) : fp_rndu(Q, n); +} + +/*!*************************************************************************** + * @brief Converting with truncation from UQx.n1 to UQx.n2. + * @details Equivalent to dividing by 2^n with truncating (throw away) the + * fractional part, resulting in an unsigned integer/fixed-point value. + * @param Q The number in (U)Qx.n1 fixed point format to be truncated. + * @param n The number of bits to be truncated, + * e.g. UQ8.8 -> UQ12.4 => n = 8 - 4 = 4. + * @return The truncated value in (U)Qx.n2 format. + *****************************************************************************/ +inline uint32_t fp_truncu(uint32_t Q, uint_fast8_t n) +{ + return (n < 32U) ? (Q >> n) : 0; +} + +/*!*************************************************************************** + * @brief Converting with truncation from Qx.n1 to Qx.n2. + * @details Equivalent to dividing by 2^n with truncating (throw away) the + * fractional part, resulting in a signed integer/fixed-point value. + * @param Q The number in (U)Qx.n1 fixed point format to be truncated. + * @param n The number of bits to be truncated, + * e.g. Q7.8 -> Q11.4 => n = 8 - 4 = 4. + * @return The truncated value in (U)Qx.n2 format. + *****************************************************************************/ +inline int32_t fp_truncs(int32_t Q, uint_fast8_t n) +{ + return (Q < 0) ? -fp_truncu(-Q, n) : fp_truncu(Q, n); +} + +/*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* FP_RND_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/int_math.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/int_math.h new file mode 100644 index 0000000000..27de8cc688 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/int_math.h @@ -0,0 +1,281 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details Provides algorithms applied to integer values. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef INT_MATH +#define INT_MATH +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @addtogroup argus_misc + * @{ + *****************************************************************************/ + +#include +#include + +/*! Enables the integer square root function. */ +#ifndef INT_SQRT +#define INT_SQRT 0 +#endif + +/*!*************************************************************************** + * @brief Integer Base-2 Logarithm. + * + * @details Calculates the base-2 logarithm for unsigned integer values. The + * result is the integer equivalent of floor(log2(x)). + * + * @param x Input parameter. + * @return The floor of the base-2 logarithm. + *****************************************************************************/ +inline uint32_t log2i(uint32_t x) +{ + assert(x != 0); +#if 1 + return 31 - __builtin_clz(x); +#else +#define S(k) if (x >= (1 << k)) { i += k; x >>= k; } + int i = 0; S(16); S(8); S(4); S(2); S(1); return i; +#undef S +#endif +} + +/*!*************************************************************************** + * @brief Integer Base-2 Logarithm with rounded result. + * + * @details Calculates the base-2 logarithm for unsigned integer values and + * returns the rounded result. The result is the integer equivalent + * of round(log2(x)). + * + * It is finding the nearest power-of-two value s.t. |x - 2^n| becomes + * minimum for all n. + * + * @param x Input parameter. + * @return The rounded value of the base-2 logarithm. + *****************************************************************************/ +inline uint32_t log2_round(uint32_t x) +{ + assert(x != 0); +#if 0 + const uint32_t y = x; + const uint32_t i = 0; + + while (y >>= 1) { i++; } + +#else + const uint32_t i = log2i(x); +#endif + return (i + ((x >> (i - 1U)) == 3U)); +} + +/*!*************************************************************************** + * @brief Finding the nearest power-of-two value. + * + * @details Implemented s.t. |x - 2^n| becomes minimum for all n. + * Special case 0: returns 0; + * Maximum input: 3037000499; higher number result in overflow! (returns 0) + * + * @param x Input parameter. + * @return Nearest power-of-two number, i.e. 2^n. + *****************************************************************************/ +inline uint32_t binary_round(uint32_t x) +{ + assert(x != 0); + const uint32_t shift = log2_round(x); + return (shift > 31U) ? 0 : 1U << shift; +} + +/*!*************************************************************************** + * @brief Counting bits set in a 32-bit unsigned integer. + * + * @details @see http://graphics.stanford.edu/~seander/bithacks.html + * + * @param x Input parameter. + * @return Number of bits set in input value. + *****************************************************************************/ +inline uint32_t popcount(uint32_t x) +{ + // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + x = x - ((x >> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + return (((x + (x >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; +} + +/*!*************************************************************************** + * @brief Determining if an integer is a power of 2 + * + * @details @see http://graphics.stanford.edu/~seander/bithacks.html + * + * @param x Input parameter. + * @return True if integer is power of 2. + *****************************************************************************/ +inline uint32_t ispowoftwo(uint32_t x) +{ + return x && !(x & (x - 1)); +} + +/*!*************************************************************************** + * @brief Calculates the absolute value. + * + * @param x Input parameter. + * @return The absolute value of x. + *****************************************************************************/ +inline uint32_t absval(int32_t x) +{ + // Note: special case of INT32_MIN must be handled correctly: + return x < 0 ? ((~(uint32_t)(x)) + 1) : (uint32_t)x; + + /* alternative with equal performance:*/ +// int32_t y = x >> 31; +// return (x ^ y) - y; + /* wrong implementation: + * does not correctly return abs(INT32_MIN) on 32-bit platform */ +// return x < 0 ? (uint32_t)(-x) : (uint32_t)x; +} + +/*!*************************************************************************** + * @brief Calculates the floor division by a factor of 2: floor(x / 2^n). + * + * @param x Input parameter. + * @param n The shift value, maximum is 31. + * @return The floor division by 2^n result. + *****************************************************************************/ +inline uint32_t floor2(uint32_t x, uint_fast8_t n) +{ + assert(n < 32); + return x >> n; +} + +/*!*************************************************************************** + * @brief Calculates the ceildiv division by a factor of 2: ceildiv(x / 2^n). + * + * @param x Input parameter. + * @param n The shift value, maximum is 31. + * @return The ceildiv division by 2^n result. + *****************************************************************************/ +inline uint32_t ceiling2(uint32_t x, uint_fast8_t n) +{ + assert(n < 32); + return x ? (1 + ((x - 1) >> n)) : 0; +} + +/*!*************************************************************************** + * @brief Calculates the ceildiv division: ceildiv(x / y). + * + * @param x Numerator + * @param y Denominator + * @return The result of the ceildiv division ceildiv(x / y). + *****************************************************************************/ +inline uint32_t ceildiv(uint32_t x, uint32_t y) +{ + assert(y != 0); + return x ? (1 + ((x - 1) / y)) : 0; +} + +/*!*************************************************************************** + * @brief Calculates the maximum of two values. + * + * @param a Input parameter. + * @param b Input parameter. + * @return The maximum value of the input parameters. + *****************************************************************************/ +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +/*!*************************************************************************** + * @brief Calculates the minimum of two values. + * + * @param a Input parameter. + * @param b Input parameter. + * @return The minimum value of the input parameters. + *****************************************************************************/ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +/*!*************************************************************************** + * @brief Clamps a value between a minimum and maximum boundary. + * + * @details Clamps the values such that the condition min <= x <= max is true. + * + * @note The condition \p min <= \p max must hold!!! + * + * @param x The input parameter to be clamped. + * @param min The minimum or lower boundary. + * @param max The maximum or upper boundary. + * @return The clamped value of the input parameter within [min,max]. + *****************************************************************************/ +#define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max))) + +#if INT_SQRT +/*!*************************************************************************** + * @brief Calculates the integer square root of x. + * + * @details The integer square root is defined as: + * isqrt(x) = (int)sqrt(x) + * + * @see https://en.wikipedia.org/wiki/Integer_square_root + * @see https://github.com/chmike/fpsqrt/blob/master/fpsqrt.c + * + * @param x Input parameter. + * @return isqrt(x) + *****************************************************************************/ +inline uint32_t isqrt(uint32_t v) +{ + unsigned t, q, b, r; + r = v; // r = v - x² + b = 0x40000000; // a² + q = 0; // 2ax + + while (b > 0) { + t = q + b; // t = 2ax + a² + q >>= 1; // if a' = a/2, then q' = q/2 + + if (r >= t) { // if (v - x²) >= 2ax + a² + r -= t; // r' = (v - x²) - (2ax + a²) + q += b; // if x' = (x + a) then ax' = ax + a², thus q' = q' + b + } + + b >>= 2; // if a' = a/2, then b' = b / 4 + } + + return q; +} +#endif // INT_SQRT + +/*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* INT_MATH */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/status.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/status.h new file mode 100644 index 0000000000..ae9a4648f5 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/status.h @@ -0,0 +1,49 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details This file contains status codes for all platform specific + * functions. + * + * @copyright + * + * Copyright (c) 2023, Broadcom Inc. + * 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 of the copyright holder 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 HOLDER 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. + *****************************************************************************/ + +#ifndef STATUS_H +#define STATUS_H +#ifdef __cplusplus +extern "C" { +#endif + +#include "api/argus_status.h" + +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* STATUS_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/time.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/time.h index 9c5f930351..1c2fe480e8 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/time.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/time.h @@ -1,11 +1,11 @@ /*************************************************************************//** * @file - * @brief This file is part of the AFBR-S50 API. - * @details This file provides utility functions for timing necessities. + * @brief This file is part of the AFBR-S50 API. + * @details This file provides utility functions for timing necessities. * * @copyright * - * Copyright (c) 2021, Broadcom Inc + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,255 +36,563 @@ #ifndef TIME_H #define TIME_H +#ifdef __cplusplus +extern "C" { +#endif /*!*************************************************************************** - * @defgroup time Time Utility - * @ingroup argusutil - * @brief Timer utilities for time measurement duties. - * @details This module provides time measurement utility functions like - * delay or time measurement methods, or time math functions. - * @addtogroup time + * @defgroup argus_time Time Utility + * @ingroup argus_util + * + * @brief Timer utilities for time measurement duties. + * + * @details This module provides time measurement utility functions like + * delay or time measurement methods, or time math functions. + * + * @addtogroup argus_time * @{ *****************************************************************************/ +#include "platform/argus_timer.h" +#include #include #include /*!*************************************************************************** - * @brief A data structure to represent current time. + * @brief A data structure to represent current time. * - * @details Value is obtained from the PIT time which must be configured as - * lifetime counter. + * @details Value is obtained from the PIT time which must be configured as + * lifetime counter. + * + * Range: [0.000000, 4294967296.999999] seconds *****************************************************************************/ -typedef struct { - /*! Seconds. */ +typedef struct ltc_t { + /*! Seconds; + * Range: [0, UINT32_MAX] seconds */ uint32_t sec; - /*! Microseconds. */ + /*! Microseconds; + * Range: [0, 999999] microseconds */ uint32_t usec; } ltc_t; /*!*************************************************************************** - * @brief Obtains the elapsed time since MCU startup. - * @param t_now returned current time + * @brief Converts #ltc_t to microseconds (uint32_t). + * @details The specified time value (type #ltc_t) is converted to microseconds. + * The value is truncated to UINT32_MAX value if the result would + * exceed UINT32_MAX microseconds. + * @param t Input #ltc_t structure. + * @return Time value in microseconds. *****************************************************************************/ -void Time_GetNow(ltc_t *t_now); +inline uint32_t Time_ToUSec(ltc_t const *t) +{ + assert(t != 0); + + // max. value to convert correctly is 4294.967295 sec (UINT32_MAX/1000000) + return ((t->sec < 4294U) || (t->sec == 4294U && t->usec < 967295U)) ? + t->usec + t->sec * 1000000U : UINT32_MAX; +} /*!*************************************************************************** - * @brief Obtains the elapsed microseconds since MCU startup. - * @details Wrap around effect due to uint32_t result format!! - * @param - - * @return Elapsed microseconds since MCU startup as uint32_t. + * @brief Converts #ltc_t to milliseconds (uint32_t). + * @details The specified time value (type #ltc_t) is converted to milliseconds. + * The value is truncated to UINT32_MAX value if the result would + * exceed UINT32_MAX milliseconds. + * The returned value is correctly rounded to the nearest value. + * @param t Input #ltc_t structure. + * @return Time value in milliseconds. *****************************************************************************/ -uint32_t Time_GetNowUSec(void); +inline uint32_t Time_ToMSec(ltc_t const *t) +{ + assert(t != 0); + + // max. value to convert correctly is 4294967.295499 sec (UINT32_MAX/1000) + return ((t->sec < 4294967U) || (t->sec == 4294967U && t->usec < 295500U)) ? + (t->usec + 500U) / 1000U + t->sec * 1000U : UINT32_MAX; +} /*!*************************************************************************** - * @brief Obtains the elapsed milliseconds since MCU startup. - * @details Wrap around effect due to uint32_t result format!! - * @param - - * @return Elapsed milliseconds since MCU startup as uint32_t. + * @brief Converts #ltc_t to seconds (uint32_t). + * @details The specified time value (type #ltc_t) is converted to seconds. + * The returned value is correctly rounded to the nearest value. + * @param t Input #ltc_t structure. + * @return Time value in seconds. *****************************************************************************/ -uint32_t Time_GetNowMSec(void); +inline uint32_t Time_ToSec(ltc_t const *t) +{ + assert(t != 0); + + // max. value to convert correctly is 4294967295.499999 sec (UINT32_MAX/1000) + return (t->sec < 4294967295U || t->usec < 500000U) ? + (t->usec + 500000U) / 1000000U + t->sec : UINT32_MAX; +} /*!*************************************************************************** - * @brief Obtains the elapsed seconds since MCU startup. - * @param - - * @return Elapsed seconds since MCU startup as uint32_t. + * @brief Converts microseconds (uint32_t) to #ltc_t. + * @details The specified time value in microseconds is converted to type #ltc_t. + * @param t Output #ltc_t structure. + * @param t_usec Input time in microseconds. *****************************************************************************/ -uint32_t Time_GetNowSec(void); +inline void Time_FromUSec(ltc_t *t, uint32_t t_usec) +{ + assert(t != 0); + t->sec = t_usec / 1000000U; + t->usec = t_usec % 1000000U; +} /*!*************************************************************************** - * @brief Obtains the elapsed time since a given time point. - * @param t_elapsed Returns the elapsed time since t_start. - * @param t_start Start time point. + * @brief Converts milliseconds (uint32_t) to #ltc_t. + * @details The specified time value in milliseconds is converted to type #ltc_t. + * @param t Output #ltc_t structure. + * @param t_msec Input time in milliseconds. *****************************************************************************/ -void Time_GetElapsed(ltc_t *t_elapsed, ltc_t const *t_start); +inline void Time_FromMSec(ltc_t *t, uint32_t t_msec) +{ + assert(t != 0); + t->sec = t_msec / 1000U; + t->usec = (t_msec % 1000U) * 1000U; +} /*!*************************************************************************** - * @brief Obtains the elapsed microseconds since a given time point. - * @details Wrap around effect due to uint32_t result format!! - * @param t_start Start time point. - * @return Elapsed microseconds since t_start as uint32_t. + * @brief Converts seconds (uint32_t) to #ltc_t. + * @details The specified time value in seconds is converted to type #ltc_t. + * @param t Output #ltc_t structure. + * @param t_sec Input time in seconds. *****************************************************************************/ -uint32_t Time_GetElapsedUSec(ltc_t const *t_start); +inline void Time_FromSec(ltc_t *t, uint32_t t_sec) +{ + assert(t != 0); + t->usec = 0; + t->sec = t_sec; +} + /*!*************************************************************************** - * @brief Obtains the elapsed milliseconds since a given time point. - * @details Wrap around effect due to uint32_t result format!! - * @param t_start Start time point. - * @return Elapsed milliseconds since t_start as uint32_t. + * @brief Checks if /p t1 is greater or equal that /p t2. + * @details Handles overflow. + * @param t1 1st operand. + * @param t2 2nd operand. + * @return Returns (t1 >= t2); *****************************************************************************/ -uint32_t Time_GetElapsedMSec(ltc_t const *t_start); +inline bool Time_GreaterEqual(ltc_t const *t1, ltc_t const *t2) +{ + assert(t1 != 0); + assert(t2 != 0); + return (t1->sec == t2->sec) ? (t1->usec >= t2->usec) : (t1->sec > t2->sec); +} + /*!*************************************************************************** - * @brief Obtains the elapsed seconds since a given time point. - * @param t_start Start time point. - * @return Elapsed seconds since t_start as uint32_t. + * @brief Obtains the elapsed time since MCU startup. + * @param t_now returned current time *****************************************************************************/ -uint32_t Time_GetElapsedSec(ltc_t const *t_start); +inline void Time_GetNow(ltc_t *t_now) +{ + assert(t_now != 0); + Timer_GetCounterValue(&(t_now->sec), &(t_now->usec)); + assert(t_now->usec < 1000000U); +} /*!*************************************************************************** - * @brief Obtains the time difference between two given time points. - * @details Result is defined as t_diff = t_end - t_start. - * Note: since no negative time differences are supported, t_end has - * to be later/larger than t_start. Otherwise, the result won't be - * a negative time span but given by max value. - * @param t_diff Returned time difference. - * @param t_start Start time point. - * @param t_end End time point. + * @brief Obtains the elapsed time since MCU startup. + * @return Returns the current time. *****************************************************************************/ -void Time_Diff(ltc_t *t_diff, ltc_t const *t_start, ltc_t const *t_end); +inline ltc_t Time_Now(void) +{ + ltc_t t_now; + Time_GetNow(&t_now); + return t_now; +} /*!*************************************************************************** - * @brief Obtains the time difference between two given time points in - * microseconds. - * @details Result is defined as t_diff = t_end - t_start. - * Refers to Time_Diff() and handles overflow such that to large - * values are limited by 0xFFFFFFFF µs. - * @param t_start Start time point. - * @param t_end End time point. - * @return Time difference in microseconds. + * @brief Obtains the elapsed microseconds since MCU startup. + * @details Wrap around effect due to uint32_t result format!! + * @return Elapsed microseconds since MCU startup as uint32_t. *****************************************************************************/ -uint32_t Time_DiffUSec(ltc_t const *t_start, ltc_t const *t_end); +inline uint32_t Time_GetNowUSec(void) +{ + ltc_t t_now = Time_Now(); + return Time_ToUSec(&t_now); +} /*!*************************************************************************** - * @brief Obtains the time difference between two given time points in - * milliseconds. - * @details Result is defined as t_diff = t_end - t_start. - * Refers to Time_Diff() and handles overflow. - * Wrap around effect due to uint32_t result format!! - * @param t_start Start time point. - * @param t_end End time point. - * @return Time difference in milliseconds. + * @brief Obtains the elapsed milliseconds (rounded) since MCU startup. + * @details Wrap around effect due to uint32_t result format!! + * @return Elapsed milliseconds since MCU startup as uint32_t. *****************************************************************************/ -uint32_t Time_DiffMSec(ltc_t const *t_start, ltc_t const *t_end); +inline uint32_t Time_GetNowMSec(void) +{ + ltc_t t_now = Time_Now(); + return Time_ToMSec(&t_now); +} /*!*************************************************************************** - * @brief Obtains the time difference between two given time points in - * seconds. - * @details Result is defined as t_diff = t_end - t_start. - * Refers to Time_Diff() and handles overflow. - * @param t_start Start time point. - * @param t_end End time point. - * @return Time difference in seconds. + * @brief Obtains the elapsed seconds (rounded) since MCU startup. + * @return Elapsed seconds since MCU startup as uint32_t. *****************************************************************************/ -uint32_t Time_DiffSec(ltc_t const *t_start, ltc_t const *t_end); +inline uint32_t Time_GetNowSec(void) +{ + ltc_t t_now = Time_Now(); + return Time_ToSec(&t_now); +} + /*!*************************************************************************** - * @brief Time delay for a given time period. - * @param dt Delay time. + * @brief Obtains the time difference between two given time points. + * @details Result is defined as t_diff = t_end - t_start. + * Note: since no negative time differences are supported, t_end has + * to be later/larger than t_start. Otherwise, the result is undefined! + * @param t_diff Returned time difference. + * @param t_start Start time point. + * @param t_end End time point. *****************************************************************************/ -void Time_Delay(ltc_t const *dt); +inline void Time_Diff(ltc_t *t_diff, ltc_t const *t_start, ltc_t const *t_end) +{ + assert(t_diff != 0); + assert(t_start != 0); + assert(t_end != 0); + assert(t_diff != t_start); + assert(t_diff != t_end); + assert(Time_GreaterEqual(t_end, t_start)); + + if (t_start->usec <= t_end->usec) { // no carry over + t_diff->sec = t_end->sec - t_start->sec; + t_diff->usec = t_end->usec - t_start->usec; + + } else { // with carry over + t_diff->sec = t_end->sec - 1 - t_start->sec; + t_diff->usec = (1000000U - t_start->usec) + t_end->usec; + } +} /*!*************************************************************************** - * @brief Time delay for a given time period in microseconds. - * @param dt_usec Delay time in microseconds. + * @brief Obtains the time difference between two given time points in + * microseconds. + * @details Result is defined as t_diff = t_end - t_start. + * Refers to Time_Diff() and handles overflow such that to large + * values are limited by 0xFFFFFFFF µs. + * @param t_start Start time point. + * @param t_end End time point. + * @return Time difference in microseconds. *****************************************************************************/ -void Time_DelayUSec(uint32_t dt_usec); +inline uint32_t Time_DiffUSec(ltc_t const *t_start, ltc_t const *t_end) +{ + ltc_t t_diff; + Time_Diff(&t_diff, t_start, t_end); + return Time_ToUSec(&t_diff); +} /*!*************************************************************************** - * @brief Time delay for a given time period in milliseconds. - * @param dt_msec Delay time in milliseconds. + * @brief Obtains the time difference between two given time points in + * milliseconds. + * @details Result is defined as t_diff = t_end - t_start. + * Refers to Time_Diff() and handles overflow. + * Wrap around effect due to uint32_t result format!! + * @param t_start Start time point. + * @param t_end End time point. + * @return Time difference in milliseconds. *****************************************************************************/ -void Time_DelayMSec(uint32_t dt_msec); +inline uint32_t Time_DiffMSec(ltc_t const *t_start, ltc_t const *t_end) +{ + ltc_t t_diff; + Time_Diff(&t_diff, t_start, t_end); + return Time_ToMSec(&t_diff); +} /*!*************************************************************************** - * @brief Time delay for a given time period in seconds. - * @param dt_sec Delay time in seconds. + * @brief Obtains the time difference between two given time points in + * seconds. + * @details Result is defined as t_diff = t_end - t_start. + * Refers to Time_Diff() and handles overflow. + * @param t_start Start time point. + * @param t_end End time point. + * @return Time difference in seconds. *****************************************************************************/ -void Time_DelaySec(uint32_t dt_sec); +inline uint32_t Time_DiffSec(ltc_t const *t_start, ltc_t const *t_end) +{ + ltc_t t_diff; + Time_Diff(&t_diff, t_start, t_end); + return Time_ToSec(&t_diff); +} + /*!*************************************************************************** - * @brief Checks if timeout is reached from a given starting time. - * @details Handles overflow. - * @param t_start Start time. - * @param t_timeout Timeout period. - * @return Timeout elapsed? True/False (boolean value) + * @brief Obtains the elapsed time since a given time point. + * @details Calculates the currently elapsed time since a specified start time + * (/p t_start). + * + * Note that /p t_start must be in the past! Otherwise, the behavior is + * undefined! + * + * @param t_elapsed Returns the elapsed time since /p t_start. + * @param t_start Start time point. *****************************************************************************/ -bool Time_CheckTimeout(ltc_t const *t_start, ltc_t const *t_timeout); +inline void Time_GetElapsed(ltc_t *t_elapsed, ltc_t const *t_start) +{ + assert(t_elapsed != 0); + assert(t_start != 0); + assert(t_elapsed != t_start); + ltc_t t_now = Time_Now(); + Time_Diff(t_elapsed, t_start, &t_now); +} /*!*************************************************************************** - * @brief Checks if timeout is reached from a given starting time. - * @details Handles overflow. - * @param t_start Start time. - * @param t_timeout_usec Timeout period in microseconds. - * @return Timeout elapsed? True/False (boolean value) + * @brief Obtains the elapsed microseconds since a given time point. + * @details Wrap around effect due to uint32_t result format!! + * @param t_start Start time point. + * @return Elapsed microseconds since t_start as uint32_t. *****************************************************************************/ -bool Time_CheckTimeoutUSec(ltc_t const *t_start, uint32_t const t_timeout_usec); +inline uint32_t Time_GetElapsedUSec(ltc_t const *t_start) +{ + assert(t_start != 0); + ltc_t t_now = Time_Now(); + return Time_DiffUSec(t_start, &t_now); +} /*!*************************************************************************** - * @brief Checks if timeout is reached from a given starting time. - * @details Handles overflow. - * @param t_start Start time. - * @param t_timeout_msec Timeout period in milliseconds. - * @return Timeout elapsed? True/False (boolean value) + * @brief Obtains the elapsed milliseconds since a given time point. + * @details Wrap around effect due to uint32_t result format!! + * @param t_start Start time point. + * @return Elapsed milliseconds since t_start as uint32_t. *****************************************************************************/ -bool Time_CheckTimeoutMSec(ltc_t const *t_start, uint32_t const t_timeout_msec); +inline uint32_t Time_GetElapsedMSec(ltc_t const *t_start) +{ + assert(t_start != 0); + ltc_t t_now = Time_Now(); + return Time_DiffMSec(t_start, &t_now); +} /*!*************************************************************************** - * @brief Checks if timeout is reached from a given starting time. - * @details Handles overflow. - * @param t_start Start time. - * @param t_timeout_sec Timeout period in seconds. - * @return Timeout elapsed? True/False (boolean value) + * @brief Obtains the elapsed seconds since a given time point. + * @param t_start Start time point. + * @return Elapsed seconds since t_start as uint32_t. *****************************************************************************/ -bool Time_CheckTimeoutSec(ltc_t const *t_start, uint32_t const t_timeout_sec); +inline uint32_t Time_GetElapsedSec(ltc_t const *t_start) +{ + assert(t_start != 0); + ltc_t t_now = Time_Now(); + return Time_DiffSec(t_start, &t_now); +} + /*!*************************************************************************** - * @brief Adds two ltc_t values. - * @details Result is defined as t = t1 + t2. Results are wrapped around at - * maximum values just like integers. - * @param t Return value: t = t1 + t2. - * @param t1 1st operand. - * @param t2 2nd operand. + * @brief Adds two #ltc_t values. + * @details Result is defined as t = t1 + t2. + * The results are wrapped around at maximum values just like integers. + * The references for t, t1 and t2 may point to the same instance(s). + * + * @param t Return value: t = t1 + t2. + * @param t1 1st operand. + * @param t2 2nd operand. *****************************************************************************/ -void Time_Add(ltc_t *t, ltc_t const *t1, ltc_t const *t2); +inline void Time_Add(ltc_t *t, ltc_t const *t1, ltc_t const *t2) +{ + assert(t != 0); + assert(t1 != 0); + assert(t2 != 0); + + t->sec = t1->sec + t2->sec; + t->usec = t1->usec + t2->usec; + + if (t->usec > 999999U) { + t->sec += 1U; + t->usec -= 1000000U; + } +} /*!*************************************************************************** - * @brief Adds a given time in microseconds to an ltc_t value. - * @param t Return value: t = t1 + t2. - * @param t1 1st operand. - * @param t2_usec 2nd operand in microseconds. + * @brief Adds a given time in microseconds to an #ltc_t value. + * @details Result is defined as t = t1 + t2. + * The results are wrapped around at maximum values just like integers. + * The references for t and t1 may point to the same instance. + * + * @param t Return value: t = t1 + t2. + * @param t1 1st operand. + * @param t2_usec 2nd operand in microseconds. *****************************************************************************/ -void Time_AddUSec(ltc_t *t, ltc_t const *t1, uint32_t t2_usec); +inline void Time_AddUSec(ltc_t *t, ltc_t const *t1, uint32_t t2_usec) +{ + assert(t != 0); + assert(t1 != 0); + ltc_t t2; + Time_FromUSec(&t2, t2_usec); + Time_Add(t, t1, &t2); +} /*!*************************************************************************** - * @brief Adds a given time in milliseconds to an ltc_t value. - * @param t Return value: t = t1 + t2. - * @param t1 1st operand. - * @param t2_msec 2nd operand in milliseconds. + * @brief Adds a given time in milliseconds to an #ltc_t value. + * @details Result is defined as t = t1 + t2. + * The results are wrapped around at maximum values just like integers. + * The references for t and t1 may point to the same instance. + * + * @param t Return value: t = t1 + t2. + * @param t1 1st operand. + * @param t2_msec 2nd operand in milliseconds. *****************************************************************************/ -void Time_AddMSec(ltc_t *t, ltc_t const *t1, uint32_t t2_msec); +inline void Time_AddMSec(ltc_t *t, ltc_t const *t1, uint32_t t2_msec) +{ + assert(t != 0); + assert(t1 != 0); + ltc_t t2; + Time_FromMSec(&t2, t2_msec); + Time_Add(t, t1, &t2); +} /*!*************************************************************************** - * @brief Adds a given time in seconds to an ltc_t value. - * @param t Return value: t = t1 + t2. - * @param t1 1st operand. - * @param t2_sec 2nd operand in seconds. + * @brief Adds a given time in seconds to an #ltc_t value. + * @details Result is defined as t = t1 + t2. + * The results are wrapped around at maximum values just like integers. + * The references for t and t1 may point to the same instance. + * + * @param t Return value: t = t1 + t2. + * @param t1 1st operand. + * @param t2_sec 2nd operand in seconds. *****************************************************************************/ -void Time_AddSec(ltc_t *t, ltc_t const *t1, uint32_t t2_sec); +inline void Time_AddSec(ltc_t *t, ltc_t const *t1, uint32_t t2_sec) +{ + assert(t != 0); + assert(t1 != 0); + ltc_t t2; + Time_FromSec(&t2, t2_sec); + Time_Add(t, t1, &t2); +} + /*!*************************************************************************** - * @brief Converts ltc_t to microseconds (uint32_t). - * @param t Input ltc_t struct. - * @return Time value in microseconds. + * @brief Checks if /p t is within the time interval /p t_start and /p t_end. + * @details The interval is from /p t_start to /p t_end. + * The function returns true if /p t >= /p t_start AND /p t < /p t_end. + * If /p t_end is before /p t_start, /p t_end is consider to be wrapped + * around and the condition inverts (i.e. the function returns true if + * /p < /p t_end OR /p t >= t_start. + * @param t_start The start of the time interval. + * @param t_end The end of the time interval. + * @param t The time to be checked if it is with the interval. + * @return True if t is within t_start and t_stop. *****************************************************************************/ -uint32_t Time_ToUSec(ltc_t const *t); +inline bool Time_CheckWithin(ltc_t const *t_start, ltc_t const *t_end, ltc_t const *t) +{ + if (Time_GreaterEqual(t_end, t_start)) { + return Time_GreaterEqual(t, t_start) && !Time_GreaterEqual(t, t_end); + + } else { + return Time_GreaterEqual(t, t_start) || !Time_GreaterEqual(t, t_end); + } +} + /*!*************************************************************************** - * @brief Converts ltc_t to milliseconds (uint32_t). - * @param t Input ltc_t struct. - * @return Time value in milliseconds. + * @brief Checks if timeout is reached from a given starting time. + * @details Checks if a specified time (/p t_timeout) has elapsed since a + * specified start time (/p t_start). + * Handles overflow/wraparound of time values at the maximum value. + * @param t_start Start time. + * @param t_timeout Timeout period. + * @return Timeout elapsed? True/False (boolean value) *****************************************************************************/ -uint32_t Time_ToMSec(ltc_t const *t); +inline bool Time_CheckTimeout(ltc_t const *t_start, ltc_t const *t_timeout) +{ + assert(t_start != 0); + assert(t_timeout != 0); + + ltc_t t_end; + ltc_t t_now = Time_Now(); + Time_Add(&t_end, t_start, t_timeout); + return !Time_CheckWithin(t_start, &t_end, &t_now); +} /*!*************************************************************************** - * @brief Converts ltc_t to seconds (uint32_t). - * @param t Input ltc_t struct. - * @return Time value in seconds. + * @brief Checks if timeout is reached from a given starting time. + * @details Handles overflow. + * @param t_start Start time. + * @param t_timeout_usec Timeout period in microseconds. + * @return Timeout elapsed? True/False (boolean value) *****************************************************************************/ -uint32_t Time_ToSec(ltc_t const *t); +inline bool Time_CheckTimeoutUSec(ltc_t const *t_start, uint32_t const t_timeout_usec) +{ + ltc_t t_timeout; + Time_FromUSec(&t_timeout, t_timeout_usec); + return Time_CheckTimeout(t_start, &t_timeout); +} + +/*!*************************************************************************** + * @brief Checks if timeout is reached from a given starting time. + * @details Handles overflow. + * @param t_start Start time. + * @param t_timeout_msec Timeout period in milliseconds. + * @return Timeout elapsed? True/False (boolean value) + *****************************************************************************/ +inline bool Time_CheckTimeoutMSec(ltc_t const *t_start, uint32_t const t_timeout_msec) +{ + ltc_t t_timeout; + Time_FromMSec(&t_timeout, t_timeout_msec); + return Time_CheckTimeout(t_start, &t_timeout); +} + +/*!*************************************************************************** + * @brief Checks if timeout is reached from a given starting time. + * @details Handles overflow. + * @param t_start Start time. + * @param t_timeout_sec Timeout period in seconds. + * @return Timeout elapsed? True/False (boolean value) + *****************************************************************************/ +inline bool Time_CheckTimeoutSec(ltc_t const *t_start, uint32_t const t_timeout_sec) +{ + ltc_t t_timeout; + Time_FromSec(&t_timeout, t_timeout_sec); + return Time_CheckTimeout(t_start, &t_timeout); +} + + +/*!*************************************************************************** + * @brief Time delay for a given time period. + * @param dt Delay time. + *****************************************************************************/ +inline void Time_Delay(ltc_t const *dt) +{ + assert(dt != 0); + ltc_t t_start = Time_Now(); + + while (!Time_CheckTimeout(&t_start, dt)); +} + +/*!*************************************************************************** + * @brief Time delay for a given time period in microseconds. + * @param dt_usec Delay time in microseconds. + *****************************************************************************/ +inline void Time_DelayUSec(uint32_t dt_usec) +{ + ltc_t t_start = Time_Now(); + + while (!Time_CheckTimeoutUSec(&t_start, dt_usec)); +} + +/*!*************************************************************************** + * @brief Time delay for a given time period in milliseconds. + * @param dt_msec Delay time in milliseconds. + *****************************************************************************/ +inline void Time_DelayMSec(uint32_t dt_msec) +{ + ltc_t t_start = Time_Now(); + + while (!Time_CheckTimeoutMSec(&t_start, dt_msec)); +} + +/*!*************************************************************************** + * @brief Time delay for a given time period in seconds. + * @param dt_sec Delay time in seconds. + *****************************************************************************/ +inline void Time_DelaySec(uint32_t dt_sec) +{ + ltc_t t_start = Time_Now(); + + while (!Time_CheckTimeoutSec(&t_start, dt_sec)); +} + /*! @} */ +#ifdef __cplusplus +} // extern "C" +#endif #endif /* TIME_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Lib/libafbrs50_m4_fpu.a b/src/drivers/distance_sensor/broadcom/afbrs50/Lib/libafbrs50_m4_fpu.a index c646ab311623e8bdb3fc3f7b7adcd503e5b0f0b3..e4e6dc0c52cd053098fa96f10f8c558d14e9c94a 100644 GIT binary patch literal 231568 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

~C?BF1K5t)`Z|LcXibc7J@t69U(A7|HJN% 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

2p`MR@I^?k=8K^Bg|?%P^2K{K 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 z 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 literal 186938 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 z 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 YLR^Lm*L6TS5cs{w&#o2XLKnXO50AvBeE -#include -#include - /******************************************************************************* * Definitions ******************************************************************************/ -/*! An error log message via print(); */ +/*! An error log message via #print function. */ #define error_log(fmt, ...) print("ERROR: " fmt "\n", ##__VA_ARGS__) /******************************************************************************* * Prototypes ******************************************************************************/ +static status_t VerifyHALImplementation(s2pi_slave_t spi_slave); + static status_t TimerPlausibilityTest(void); static status_t TimerWraparoundTest(void); static status_t SpiConnectionTest(s2pi_slave_t slave); -static status_t SpiInterruptTest(s2pi_slave_t slave); +static status_t SpiMaxLengthTest(s2pi_slave_t slave); +//static status_t SpiInterruptTest(s2pi_slave_t slave); +static status_t GpioInterruptTest(s2pi_slave_t slave); static status_t GpioModeTest(s2pi_slave_t slave); static status_t TimerTest(s2pi_slave_t slave); static status_t PITTest(void); +static status_t SpiTransferFromInterruptTest(s2pi_slave_t slave); static status_t CheckTimerCounterValues(uint32_t hct, uint32_t lct); -static status_t SPITransferSync(s2pi_slave_t slave, uint8_t *data, uint8_t size); +static status_t SPITransferSync(s2pi_slave_t slave, uint8_t *data, size_t size); static status_t ConfigureDevice(s2pi_slave_t slave, int8_t rcoTrim); -static status_t TriggerMeasurement(s2pi_slave_t slave, uint16_t samples); -static status_t AwaitDataReady(s2pi_slave_t slave, uint32_t timeout_ms); +static status_t TriggerMeasurement(s2pi_slave_t slave, uint16_t samples, s2pi_callback_t callback, void *callbackData); static status_t ReadEEPROM(s2pi_slave_t slave, uint8_t *eeprom); static status_t ReadRcoTrim(s2pi_slave_t slave, int8_t *RcoTrim); static status_t RunMeasurement(s2pi_slave_t slave, uint16_t samples); static status_t RunPITTest(uint32_t exp_dt_us, uint32_t n); static void PIT_Callback(void *param); -static void DataReadyCallback(void *param); +static void GPIO_Callback(void *param); /// @cond EXTERN extern uint32_t EEPROM_ReadChipId(uint8_t const *eeprom); -extern argus_module_version_t EEPROM_ReadModule(uint8_t const *eeprom); +extern uint8_t EEPROM_ReadModule(uint8_t const *eeprom); extern status_t EEPROM_Read(s2pi_slave_t slave, uint8_t address, uint8_t *data); extern uint8_t hamming_decode(uint8_t const *code, uint8_t *data); /// @endcond @@ -98,99 +102,129 @@ extern uint8_t hamming_decode(uint8_t const *code, uint8_t *data); status_t Argus_VerifyHALImplementation(s2pi_slave_t spi_slave) { - status_t status = STATUS_OK; + print("########################################################\n"); + print("# Running HAL Verification Test - " HAL_TEST_VERSION "\n"); + print("########################################################\n"); + print("- SPI Slave: %d \n\n", spi_slave); - PX4_INFO_RAW("########################################################\n"); - PX4_INFO_RAW("# Running HAL Verification Test - " HAL_TEST_VERSION "\n"); - PX4_INFO_RAW("########################################################\n\n"); + const status_t status = VerifyHALImplementation(spi_slave); - PX4_INFO_RAW("1 > Timer Plausibility Test\n"); - status = TimerPlausibilityTest(); - - if (status != STATUS_OK) { goto summary; } - - PX4_INFO_RAW("1 > PASS\n\n"); - - PX4_INFO_RAW("2 > Timer Wraparound Test\n"); - status = TimerWraparoundTest(); - - if (status != STATUS_OK) { goto summary; } - - PX4_INFO_RAW("2 > PASS\n\n"); - - PX4_INFO_RAW("3 > SPI Connection Test\n"); - status = SpiConnectionTest(spi_slave); - - if (status != STATUS_OK) { goto summary; } - - PX4_INFO_RAW("3 > PASS\n\n"); - - PX4_INFO_RAW("4 > SPI Interrupt Test\n"); - status = SpiInterruptTest(spi_slave); - - if (status != STATUS_OK) { goto summary; } - - PX4_INFO_RAW("4 > PASS\n\n"); - - PX4_INFO_RAW("5 > GPIO Mode Test\n"); - status = GpioModeTest(spi_slave); - - if (status != STATUS_OK) { goto summary; } - - PX4_INFO_RAW("5 > PASS\n\n"); - - PX4_INFO_RAW("6 > Lifetime Counter Timer (LTC) Test\n"); - status = TimerTest(spi_slave); - - if (status != STATUS_OK) { goto summary; } - - PX4_INFO_RAW("6 > PASS\n\n"); - - PX4_INFO_RAW("7 > Periodic Interrupt Timer (PIT) Test\n"); - status = PITTest(); - - if (status == ERROR_NOT_IMPLEMENTED) { - PX4_INFO_RAW("7 > SKIPPED (PIT is not implemented)\n\n"); - - } else { - if (status != STATUS_OK) { goto summary; } - - PX4_INFO_RAW("7 > PASS\n\n"); - } - - -summary: - PX4_INFO_RAW("########################################################\n"); + print("########################################################\n"); if (status != STATUS_OK) { - PX4_INFO_RAW("# FAIL: HAL Verification Test finished with error %d!\n", (int)status); + print("# FAIL: HAL Verification Test finished with error %d!\n", status); } else { - PX4_INFO_RAW("# PASS: HAL Verification Test finished successfully!\n"); + print("# PASS: HAL Verification Test finished successfully!\n"); } - PX4_INFO_RAW("########################################################\n\n"); + print("########################################################\n\n"); + return status; } /*!*************************************************************************** - * @brief Checks the validity of timer counter values. + * @brief Executes a series of tests in order to verify the HAL implementation. * - * @details This verifies that the counter values returned from the - * #Timer_GetCounterValue function are valid. This means, the low - * counter value \p lct is within 0 and 999999 µs. + * @details See #Argus_VerifyHALImplementation for details. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_FAIL on failure (check the error log for more information). + * @param spi_slave The SPI hardware slave. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +static status_t VerifyHALImplementation(s2pi_slave_t spi_slave) +{ + status_t status = STATUS_OK; + + print("1 > Timer Plausibility Test\n"); + status = TimerPlausibilityTest(); + + if (status != STATUS_OK) { return status; } + + print("1 > PASS\n\n"); + + print("2 > Timer Wraparound Test\n"); + status = TimerWraparoundTest(); + + if (status != STATUS_OK) { return status; } + + print("2 > PASS\n\n"); + + print("3 > SPI Connection Test\n"); + status = SpiConnectionTest(spi_slave); + + if (status != STATUS_OK) { return status; } + + print("3 > PASS\n\n"); + + print("4 > SPI Maximum Data Length Test\n"); + status = SpiMaxLengthTest(spi_slave); + + if (status != STATUS_OK) { return status; } + + print("4 > PASS\n\n"); + + print("5 > GPIO Interrupt Test\n"); + status = GpioInterruptTest(spi_slave); + + if (status != STATUS_OK) { return status; } + + print("5 > PASS\n\n"); + + print("6 > GPIO Mode Test\n"); + status = GpioModeTest(spi_slave); + + if (status != STATUS_OK) { return status; } + + print("6 > PASS\n\n"); + + print("7 > Lifetime Counter Timer (LTC) Test\n"); + status = TimerTest(spi_slave); + + if (status != STATUS_OK) { return status; } + + print("7 > PASS\n\n"); + + print("8 > Periodic Interrupt Timer (PIT) Test\n"); + status = PITTest(); + + if (status == ERROR_NOT_IMPLEMENTED) { + print("8 > SKIPPED (PIT is not implemented)\n\n"); + + } else { + if (status != STATUS_OK) { return status; } + + print("8 > PASS\n\n"); + } + + print("9 > SPI Interrupt Test\n"); + status = SpiTransferFromInterruptTest(spi_slave); + + if (status != STATUS_OK) { return status; } + + print("9 > PASS\n\n"); + + return status; +} + +/*!*************************************************************************** + * @brief Checks the validity of timer counter values. + * + * @details This verifies that the counter values returned from the + * #Timer_GetCounterValue function are valid. This means, the low + * counter value \p lct is within 0 and 999999 µs. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_FAIL on failure (check the error log for more information). *****************************************************************************/ static status_t CheckTimerCounterValues(uint32_t hct, uint32_t lct) { if (lct > 999999) { - PX4_INFO_RAW("Timer plausibility check:\n" - "The parameter \"lct\" of Timer_GetCounterValue() must always " - "be within 0 and 999999.\n" - "Current Values: hct = %d, lct = %d", (uint)hct, (uint)lct); + error_log("Timer plausibility check:\n" + "The parameter \"lct\" of Timer_GetCounterValue() must always " + "be within 0 and 999999.\n" + "Current Values: hct = %d, lct = %d", hct, lct); return ERROR_FAIL; } @@ -198,24 +232,24 @@ static status_t CheckTimerCounterValues(uint32_t hct, uint32_t lct) } /*!*************************************************************************** - * @brief Plausibility Test for Timer HAL Implementation. + * @brief Plausibility Test for Timer HAL Implementation. * - * @details Rudimentary tests the lifetime counter (LTC) implementation. - * This verifies that the LTC is running by checking if the returned - * values of two consecutive calls to the #Timer_GetCounterValue - * function are ascending. An artificial delay using the NOP operation - * is induced such that the timer is not read to fast. + * @details Rudimentary tests the lifetime counter (LTC) implementation. + * This verifies that the LTC is running by checking if the returned + * values of two consecutive calls to the #Timer_GetCounterValue + * function are ascending. An artificial delay using the NOP operation + * is induced such that the timer is not read to fast. * * @warning If using an ultra-fast processor with a rather low timer granularity, - * the test may fail! In this case, it could help to increase the delay - * by increasing the for-loop exit criteria. + * the test may fail! In this case, it could help to increase the delay + * by increasing the for-loop exit criteria. * - * @warning This test does not test yet verify if the timing is correct at all! - * This it done in later test... + * @warning This test does not test yet verify if the timing is correct at all! + * This it done in later test... * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_FAIL on failure (check the error log for more information). + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_FAIL on failure (check the error log for more information). *****************************************************************************/ static status_t TimerPlausibilityTest(void) { @@ -230,7 +264,7 @@ static status_t TimerPlausibilityTest(void) /* Check max value is not exceeded for LCT timer (us) */ status_t status = CheckTimerCounterValues(hct0, lct0); - if (status < STATUS_OK) { return status; } + if (status != STATUS_OK) { return status; } /* Adding a delay. Depending on MCU speed, this takes any time. * However, the Timer should be able to solve this on any MCU. */ @@ -242,18 +276,18 @@ static status_t TimerPlausibilityTest(void) /* Check max value is not exceeded for LCT timer (us) */ status = CheckTimerCounterValues(hct1, lct1); - if (status < STATUS_OK) { return status; } + if (status != STATUS_OK) { return status; } /* Either the hct value must have been increased or the lct value if the hct * value is still the same. */ if (!((hct1 > hct0) || ((hct1 == hct0) && (lct1 > lct0)))) { - PX4_INFO_RAW("Timer plausibility check: the elapsed time could not be " - "measured with the Timer_GetCounterValue() function; no time " - "has elapsed!\n" - "The delay was induced by the following code:\n" - "for (volatile uint32_t i = 0; i < 100000; ++i) __asm(\"nop\");\n" - "Current Values: hct0 = %d, lct0 = %d, hct1 = %d, lct1 = %d", - (uint)hct0, (uint)lct0, (uint)hct1, (uint)lct1); + error_log("Timer plausibility check: the elapsed time could not be " + "measured with the Timer_GetCounterValue() function; no time " + "has elapsed!\n" + "The delay was induced by the following code:\n" + "for (volatile uint32_t i = 0; i < 100000; ++i) __asm(\"nop\");\n", + "Current Values: hct0 = %d, lct0 = %d, hct1 = %d, lct1 = %d", + hct0, lct0, hct1, lct1); return ERROR_FAIL; } @@ -261,29 +295,29 @@ static status_t TimerPlausibilityTest(void) } /*!*************************************************************************** - * @brief Wraparound Test for the Timer HAL Implementation. + * @brief Wraparound Test for the Timer HAL Implementation. * * @details The LTC values must wrap from 999999 µs to 0 µs and increase the * seconds counter accordingly. This test verifies the correct wrapping * by consecutively calling the #Timer_GetCounterValue function until * at least 2 wraparound events have been occurred. * - * @note This test requires the timer to basically run and return ascending - * values. Also, if the timer is too slow, this may take very long! - * Usually, the test takes 2 seconds, since 2 wraparound events are - * verified. + * @note This test requires the timer to basically run and return ascending + * values. Also, if the timer is too slow, this may take very long! + * Usually, the test takes 2 seconds, since 2 wraparound events are + * verified. * - * @warning This test does not test yet verify if the timing is correct at all! - * This it done in later test... + * @warning This test does not test yet verify if the timing is correct at all! + * This it done in later test... * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_FAIL on failure (check the error log for more information). + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_FAIL on failure (check the error log for more information). *****************************************************************************/ static status_t TimerWraparoundTest(void) { /* Test parameter configuration: *****************************************/ - const int8_t n = 2; // The number of wraparounds to test. + const uint8_t n = 2; // The number of wraparounds to test. /*************************************************************************/ uint32_t hct0 = 0; @@ -297,14 +331,12 @@ static status_t TimerWraparoundTest(void) /* Check max value is not exceeded for LCT timer (us) */ status_t status = CheckTimerCounterValues(hct0, lct0); - if (status < STATUS_OK) { return status; } + if (status != STATUS_OK) { return status; } /* Set end after 2 seconds, i.e. 2 wrap around events. */ uint32_t hct2 = hct0 + n; uint32_t lct2 = lct0; - px4_usleep(20000); - /* Periodically read timer values. From previous tests we * already know the timer value is increasing. */ while (hct0 < hct2 || lct0 < lct2) { @@ -315,69 +347,92 @@ static status_t TimerWraparoundTest(void) /* Check max value is not exceeded for LCT timer (us) */ status = CheckTimerCounterValues(hct0, lct0); - if (status < STATUS_OK) { return status; } + if (status != STATUS_OK) { return status; } /* Testing if calls to Timer_GetCounterValue are equal or increasing. * Also testing if wraparound is correctly handled. * Assumption here is that two sequential calls to the get functions are - * only a few µs appart! I.e. if hct wraps, the new lct must be smaller + * only a few µs apart! I.e. if hct wraps, the new lct must be smaller * than previous one. */ if (!(((hct1 == hct0 + 1) && (lct1 < lct0)) || ((hct1 == hct0) && (lct1 >= lct0)))) { - PX4_INFO_RAW("Timer plausibility check: the wraparound of \"lct\" or " - "\"hct\" parameters of the Timer_GetCounterValue() " - "function was not handled correctly!\n" - "Current Values: hct0 = %d, lct0 = %d, hct1 = %d, lct1 = %d", - (uint)hct0, (uint)lct0, (uint)hct1, (uint)lct1); + error_log("Timer plausibility check: the wraparound of \"lct\" or " + "\"hct\" parameters of the Timer_GetCounterValue() " + "function was not handled correctly!\n" + "Current Values: hct0 = %d, lct0 = %d, hct1 = %d, lct1 = %d", + hct0, lct0, hct1, lct1); return ERROR_FAIL; } hct0 = hct1; lct0 = lct1; - - px4_usleep(20000); } return STATUS_OK; } /*!*************************************************************************** - * @brief Helper function for transfer data to SPI in blocking mode. + * @brief SPI interrupt callback function for the SPI transfer interrupt test. + * + * @details The interrupt callback is invoked from the S2PI module upon + * finishing the SPI transfer. The callback is used by the + * #SPITransferSync helper function to retrieve the status of the + * SPI transfer. + * + * @param status The S2PI module status passed to the callback. + * @param param The abstract interrupt callback parameter. + * + * @return Returns #STATUS_OK. + *****************************************************************************/ +static status_t SpiTransferInterruptCallback(status_t status, void *param) +{ + *((status_t *)param) = status; + return STATUS_OK; +} + +/*!*************************************************************************** + * @brief Helper function for transfer data to SPI in blocking mode. * * @details Calls the #S2PI_TransferFrame function and waits until the transfer - * has been finished by checking the #S2PI_GetStatus return code to - * become #STATUS_IDLE (or #STATUS_OK). + * has been finished by checking the #S2PI_GetStatus return code to + * become #STATUS_IDLE (or #STATUS_OK). * - * @warning The test utilizes already the timer HAL in order to implement a - * rudimentary timeout. However, at this time, only some basic - * plausibility checks are performed on the timer HAL. I.e. if there - * is an issue in the time HAL, e.g. too fast or too slow time - * counting, the test may fail with an #ERROR_TIMEOUT. In this case, - * one also needs to verify the timer HAL, especially the - * #Timer_GetCounterValue function. + * @warning The test utilizes already the timer HAL in order to implement a + * rudimentary timeout. However, at this time, only some basic + * plausibility checks are performed on the timer HAL. I.e. if there + * is an issue in the time HAL, e.g. too fast or too slow time + * counting, the test may fail with an #ERROR_TIMEOUT. In this case, + * one also needs to verify the timer HAL, especially the + * #Timer_GetCounterValue function. * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. - * @param data The data array to be transfered. - * @param size The size of the data array to be transfered. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @param data The data array to be transferred. + * @param size The size of the data array to be transferred. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_TIMEOUT if the operation did not finished within a specified - * time (check also timer HAL implementation). - * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus - * return any negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if the operation did not finished within a specified + * time (check also timer HAL implementation). + * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus + * return any negative status. *****************************************************************************/ -static status_t SPITransferSync(s2pi_slave_t slave, uint8_t *data, uint8_t size) +static status_t SPITransferSync(s2pi_slave_t slave, uint8_t *data, size_t size) { /* Test parameter configuration: *****************************************/ - const uint32_t timeout_ms = 100; // The transfer timeout in ms. + const uint32_t timeout_ms = 100; // The transfer timeout in ms. /*************************************************************************/ - status_t status = S2PI_TransferFrame(slave, data, data, size, 0, 0); + /* The status will be changed in the SPI callback. */ + volatile status_t callbackStatus = STATUS_BUSY; - if (status < STATUS_OK) { - PX4_INFO_RAW("SPI transfer failed! The call to S2PI_TransferFrame " - "yielded error code: %d", (int)status); + status_t status = S2PI_TransferFrame(slave, data, data, size, + SpiTransferInterruptCallback, + (void *)&callbackStatus); + + if (status != STATUS_OK) { + error_log("SPI transfer failed! The call to S2PI_TransferFrame " + "yielded error code: %d", + status); return status; } @@ -388,53 +443,67 @@ static status_t SPITransferSync(s2pi_slave_t slave, uint8_t *data, uint8_t size) Time_GetNow(&start); do { - status = S2PI_GetStatus(); + status = S2PI_GetStatus(slave); if (status < STATUS_OK) { - PX4_INFO_RAW("SPI transfer failed! The call to S2PI_GetStatus " - "yielded error code: %d", (int)status); - S2PI_Abort(); + error_log("SPI transfer failed! The call to S2PI_GetStatus " + "yielded error code: %d", status); + S2PI_Abort(slave); return status; } if (Time_CheckTimeoutMSec(&start, timeout_ms)) { - PX4_INFO_RAW("SPI transfer failed! The operation did not finished " - "within %u ms. This may also be caused by an invalid " - "timer implementation!", (uint)timeout_ms); + error_log("SPI transfer failed! The operation did not finished " + "within %d ms. This may also be caused by an invalid " + "timer implementation!", timeout_ms); return ERROR_TIMEOUT; } } while (status == STATUS_BUSY); + if (callbackStatus != STATUS_OK) { + error_log("Invocation of the SPI callback failed! The SPI transfer " + "callback yielded error code: %d", callbackStatus); + return callbackStatus; + } + return status; } /*!*************************************************************************** - * @brief SPI Connection Test for S2PI HAL Implementation. + * @brief SPI Connection Test for S2PI HAL Implementation. * * @details This test verifies the basic functionality of the SPI interface. - * The test utilizes the devices laser pattern register, which can - * be freely programmed by any 128-bit pattern. Thus, it writes a byte - * sequence and reads back the written values on the consecutive SPI - * access. * - * @warning The test utilizes already the timer HAL in order to implement a - * rudimentary timeout. However, at this time, only some basic - * plausibility checks are performed on the timer HAL. I.e. if there - * is an issue in the time HAL, e.g. too fast or too slow time - * counting, the test may fail with an #ERROR_TIMEOUT. In this case, - * one also needs to verify the timer HAL, especially the - * #Timer_GetCounterValue function. + * The test utilizes the devices laser pattern register, which can + * be freely programmed by any 128-bit pattern. Thus, it writes a byte + * sequence and reads back the written values on the consecutive SPI + * access. * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * Note: The test verifies the SPI interface transfer functionality + * in blocking mode and also verifies the interrupt callback. + * In order to wait for the transfer to finish, it reads the S2PI + * status in a loop. If the status does not change to #STATUS_IDLE, + * the test will fail with an #ERROR_TIMEOUT. Finally, the test will + * verify the SPI transfer callback status. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_TIMEOUT if the operation did not finished within a specified - * time (check also timer HAL implementation). - * - #ERROR_FAIL if the device access failed and the read data did not - * match the expected values. - * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus - * return any negative status. + * @warning The test utilizes already the timer HAL in order to implement a + * rudimentary timeout. However, at this time, only some basic + * plausibility checks are performed on the timer HAL. I.e. if there + * is an issue in the time HAL, e.g. too fast or too slow time + * counting, the test may fail with an #ERROR_TIMEOUT. In this case, + * one also needs to verify the timer HAL, especially the + * #Timer_GetCounterValue function. + * + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if the operation did not finished within a specified + * time (check also timer HAL implementation). + * - #ERROR_FAIL if the device access failed and the read data did not + * match the expected values. + * - The S2PI layer error code if #S2PI_TransferFrame, #S2PI_GetStatus + * or the SPI callback yield any negative status. *****************************************************************************/ static status_t SpiConnectionTest(s2pi_slave_t slave) { @@ -448,8 +517,8 @@ static status_t SpiConnectionTest(s2pi_slave_t slave) status = SPITransferSync(slave, data, 17U); - if (status < STATUS_OK) { - PX4_INFO_RAW("SPI connection test failed!"); + if (status != STATUS_OK) { + error_log("SPI connection test failed!"); return status; } @@ -460,18 +529,18 @@ static status_t SpiConnectionTest(s2pi_slave_t slave) status = SPITransferSync(slave, data, 17U); - if (status < STATUS_OK) { - PX4_INFO_RAW("SPI connection test failed!"); + if (status != STATUS_OK) { + error_log("SPI connection test failed!"); return status; } /* Verify the read pattern. */ for (uint8_t i = 1; i < 17U; ++i) { if (data[i] != i) { - PX4_INFO_RAW("SPI connection test failed!\n" - "Verification of read data is invalid!\n" - "read_data[%d] = %d, but expected was %d", - i, data[i], i); + error_log("SPI connection test failed!\n" + "Verification of read data is invalid!\n" + "read_data[%d] = %d, but expected was %d", + i, data[i], i); return ERROR_FAIL; } } @@ -479,142 +548,236 @@ static status_t SpiConnectionTest(s2pi_slave_t slave) return STATUS_OK; } - /*!*************************************************************************** - * @brief The data ready callback invoked by the API. + * @brief Maximum SPI Data Size Test for S2PI HAL Implementation. * - * @details The callback is invoked by the API when the device GPIO IRQ is - * pending after a measurement has been executed and data is ready to - * be read from the device. + * @details This test verifies the maximum data transfer length of the SPI + * interface. The test sends and receives up to 396 data bytes plus + * a single address byte over the SPI interface and verifies that no + * data get lost. * - * @param param The abstract pointer to the boolean value that determines if - * the callback is invoked. + * The test utilizes the channel select register which is 3 bytes plus + * address. This register can be repeatedly written with any pattern + * using the DMA mode. The register is written 100 times in a row + * to verify that long data frames with up to 400 bytes can be + * transmitted. + * + * Note that this test was motivated by an invalid implementation that + * used uint8_t type for the frame length in the #S2PI_TransferFrame + * function instead of an uint16_t value. This resulted in a maximum + * data length of 141 bytes (367 & 0xFF = 141) when reading the + * data value register. + * + * @warning The test utilizes already the timer HAL in order to implement a + * rudimentary timeout. However, at this time, only some basic + * plausibility checks are performed on the timer HAL. I.e. if there + * is an issue in the time HAL, e.g. too fast or too slow time + * counting, the test may fail with an #ERROR_TIMEOUT. In this case, + * one also needs to verify the timer HAL, especially the + * #Timer_GetCounterValue function. + * + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if the operation did not finished within a specified + * time (check also timer HAL implementation). + * - #ERROR_FAIL if the device access failed and the read data did not + * match the expected values. + * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus + * return any negative status. *****************************************************************************/ -static void DataReadyCallback(void *param) +static status_t SpiMaxLengthTest(s2pi_slave_t slave) { - IRQ_LOCK(); - *((bool *) param) = true; - IRQ_UNLOCK(); + status_t status = STATUS_OK; + uint8_t data[400U] = { 0 }; + + /* Setup device (enable DMA mode). */ + data[0] = 0x10; data[1] = 0x12; + status = SPITransferSync(slave, data, 2); + + if (status != STATUS_OK) { + error_log("Device configuration failed!"); + return status; + } + + data[0] = 0x12; data[1] = 0x00; data[2] = 0x2B; + status = SPITransferSync(slave, data, 3); + + if (status != STATUS_OK) { + error_log("Device configuration failed!"); + return status; + } + + /* Transfer a pattern to the register */ + for (uint32_t i = 0; i < sizeof(data); i += 4) { + data[i + 0] = 0x1E; // Address + data[i + 1] = (uint8_t)i; // Random Data Byte 0 + data[i + 2] = (uint8_t)(i + 1); // Random Data Byte 1 + data[i + 3] = (uint8_t)(i * 2); // Random Data Byte 2 + } + + status = SPITransferSync(slave, data, sizeof(data)); + + if (status != STATUS_OK) { + error_log("SPI maximum data length test failed!"); + return status; + } + + /* Repeat ... */ + for (uint32_t i = 0; i < sizeof(data); i += 4) { + data[i + 0] = 0x1E; // Address + data[i + 1] = (uint8_t)i; // Random Data Byte 0 + data[i + 2] = (uint8_t)(i + 1); // Random Data Byte 1 + data[i + 3] = (uint8_t)(i * 2); // Random Data Byte 2 + } + + status = SPITransferSync(slave, data, sizeof(data)); + + if (status != STATUS_OK) { + error_log("SPI maximum data length test failed!"); + return status; + } + + /* Verify the read pattern; skip all address bytes. */ + for (uint32_t i = 0; i < sizeof(data); i += 4) { + uint32_t j = (i + 4) % sizeof(data); + + if (data[j + 1] != (uint8_t)i + || data[j + 2] != (uint8_t)(i + 1) + || data[j + 3] != (uint8_t)(i * 2)) { + error_log("SPI maximum data length test failed!\n" + "Verification of read data is invalid at byte %d!\n" + " - expected: 0x%02X%02X%02X\n" + " - actual: 0x%02X%02X%02X", + i, (uint8_t)i, (uint8_t)(i + 1), (uint8_t)(i * 2), + data[j + 1], data[j + 2], data[j + 3]); + return ERROR_FAIL; + } + } + + return STATUS_OK; } /*!*************************************************************************** - * @brief Configures the device with a bare minimum setup to run the tests. + * @brief Configures the device with a bare minimum setup to run the tests. * - * @details This function applies a number of configuration values to the - * device, such that a pseudo measurement w/o laser output can be - * performed. + * @details This function applies a number of configuration values to the + * device, such that a pseudo measurement w/o laser output can be + * performed. * - * A \p rcoTrim parameter can be passed to adjust the actual clock - * setup. + * A \p rcoTrim parameter can be passed to adjust the actual clock + * setup. * - * @warning The test utilizes already the timer HAL in order to implement a - * rudimentary timeout. However, at this time, only some basic - * plausibility checks are performed on the timer HAL. I.e. if there - * is an issue in the time HAL, e.g. too fast or too slow time - * counting, the test may fail with an #ERROR_TIMEOUT. In this case, - * one also needs to verify the timer HAL, especially the - * #Timer_GetCounterValue function. + * @warning The test utilizes already the timer HAL in order to implement a + * rudimentary timeout. However, at this time, only some basic + * plausibility checks are performed on the timer HAL. I.e. if there + * is an issue in the time HAL, e.g. too fast or too slow time + * counting, the test may fail with an #ERROR_TIMEOUT. In this case, + * one also needs to verify the timer HAL, especially the + * #Timer_GetCounterValue function. * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. - * @param rcoTrim The RCO Trimming value added to the nominal RCO register - * value. Pass 0 if no fine tuning is required. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @param rcoTrim The RCO Trimming value added to the nominal RCO register + * value. Pass 0 if no fine tuning is required. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_TIMEOUT if the SPI operation did not finished within a - * specified time (check also timer HAL implementation). - * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus - * return any negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if the SPI operation did not finished within a + * specified time (check also timer HAL implementation). + * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus + * return any negative status. *****************************************************************************/ static status_t ConfigureDevice(s2pi_slave_t slave, int8_t rcoTrim) { /* Setup Device and Trigger Measurement. */ - uint16_t v = 0x0010U | (((34 + rcoTrim) & 0x3F) << 6); - uint8_t d1[] = { 0x14, v >> 8, v & 0xFF, 0x21 }; + assert(rcoTrim >= -34 && rcoTrim < 0x3F - 34); + const uint16_t v = (uint16_t)(0x0010U | (((uint16_t)(34 + rcoTrim) & 0x3F) << 6U)); + uint8_t d1[] = { 0x14U, (uint8_t)(v >> 8U), v & 0xFFU, 0x21U }; status_t status = SPITransferSync(slave, d1, sizeof(d1)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d2[] = { 0x16, 0x7F, 0xFF, 0x7F, 0xE9 }; status = SPITransferSync(slave, d2, sizeof(d2)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d3[] = { 0x18, 0x00, 0x00, 0x03 }; status = SPITransferSync(slave, d3, sizeof(d3)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d4[] = { 0x10, 0x12 }; status = SPITransferSync(slave, d4, sizeof(d4)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d5[] = { 0x12, 0x00, 0x2B }; status = SPITransferSync(slave, d5, sizeof(d5)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d6[] = { 0x08, 0x04, 0x84, 0x10 }; status = SPITransferSync(slave, d6, sizeof(d6)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d7[] = { 0x0A, 0xFE, 0x51, 0x0F, 0x05 }; status = SPITransferSync(slave, d7, sizeof(d7)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d8[] = { 0x0C, 0x00, 0x00, 0x00 }; status = SPITransferSync(slave, d8, sizeof(d8)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d9[] = { 0x1E, 0x00, 0x00, 0x00 }; status = SPITransferSync(slave, d9, sizeof(d9)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d10[] = { 0x20, 0x01, 0xFF, 0xFF }; status = SPITransferSync(slave, d10, sizeof(d10)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } uint8_t d11[] = { 0x22, 0xFF, 0xFF, 0x04 }; status = SPITransferSync(slave, d11, sizeof(d11)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Device configuration failed!"); + if (status != STATUS_OK) { + error_log("Device configuration failed!"); return status; } @@ -622,40 +785,55 @@ static status_t ConfigureDevice(s2pi_slave_t slave, int8_t rcoTrim) } /*!*************************************************************************** - * @brief Triggers a measurement on the device with specified sample count. + * @brief Triggers a measurement on the device with specified sample count. * * @details The function triggers a measurement cycle on the device. A - * \p sample count can be specified to setup individual number of - * digital averaging. + * \p sample count can be specified to setup individual number of + * digital averaging. * - * @warning The test utilizes already the timer HAL in order to implement a - * rudimentary timeout. However, at this time, only some basic - * plausibility checks are performed on the timer HAL. I.e. if there - * is an issue in the time HAL, e.g. too fast or too slow time - * counting, the test may fail with an #ERROR_TIMEOUT. In this case, - * one also needs to verify the timer HAL, especially the - * #Timer_GetCounterValue function. + * The measurement in triggered asynchronously without waiting + * for any event to finish. * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. - * @param samples The specified number of averaging samples for the measurement. + * @warning The test utilizes already the timer HAL in order to implement a + * rudimentary timeout. However, at this time, only some basic + * plausibility checks are performed on the timer HAL. I.e. if there + * is an issue in the time HAL, e.g. too fast or too slow time + * counting, the test may fail with an #ERROR_TIMEOUT. In this case, + * one also needs to verify the timer HAL, especially the + * #Timer_GetCounterValue function. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_TIMEOUT if the operation did not finished within a specified - * time (check also timer HAL implementation). - * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus - * return any negative status. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @param samples The specified number of averaging samples for the measurement. + * @param callback An optional SPI callback. + * @param callbackData The optional callback data parameter. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if the operation did not finished within a specified + * time (check also timer HAL implementation). + * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus + * return any negative status. *****************************************************************************/ -static status_t TriggerMeasurement(s2pi_slave_t slave, uint16_t samples) +static status_t TriggerMeasurement(s2pi_slave_t slave, uint16_t samples, + s2pi_callback_t callback, void *callbackData) { // samples is zero based, i.e. writing 0 yields 1 sample samples = samples > 0 ? samples - 1 : samples; - uint16_t v = 0x8000U | ((samples & 0x03FFU) << 5U); - uint8_t d[] = { 0x1C, v >> 8, v & 0xFFU }; - status_t status = SPITransferSync(slave, d, sizeof(d)); + const uint16_t v = (uint16_t)(0x8000U | ((samples & 0x03FFU) << 5U)); - if (status < STATUS_OK) { - PX4_INFO_RAW("Trigger measurement failed!"); + // data is static as the transfer is asynchronous and the buffer must persist. + static uint8_t data[] = { 0x1CU, 0x00U, 0x00U }; + data[0] = 0x1CU; + data[1] = (uint8_t)(v >> 8U); + data[2] = v & 0xFFU; + + status_t status = S2PI_TransferFrame(slave, data, data, sizeof(data), + callback, callbackData); + + if (status != STATUS_OK) { + error_log("SPI transfer failed to trigger measurements! " + "The call to S2PI_TransferFrame yielded error code: %d", + status); return status; } @@ -663,162 +841,263 @@ static status_t TriggerMeasurement(s2pi_slave_t slave, uint16_t samples) } /*!*************************************************************************** - * @brief Waits for the data ready interrupt to be pending. + * @brief Data structure for the GPIO interrupt test. * - * @details The function polls the current interrupt pending state of the data - * ready interrupt from the device, i.e. reads the IRQ GPIO pin until - * it is pulled to low by the device. - * - * - * @warning The test utilizes already the timer HAL in order to implement a - * rudimentary timeout. However, at this time, only some basic - * plausibility checks are performed on the timer HAL. I.e. if there - * is an issue in the time HAL, e.g. too fast or too slow time - * counting, the test may fail with an #ERROR_TIMEOUT. In this case, - * one also needs to verify the timer HAL, especially the - * #Timer_GetCounterValue function. - * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. - * @param timeout_ms The timeout to cancel waiting for the IRQ. - * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_TIMEOUT if either the SPI operation did not finished - * or the IRQ was not detected within a specified time (check also - * timer HAL implementation). - * - The S2PI layer error code if #S2PI_TransferFrame, #S2PI_GetStatus - * or #S2PI_SetIrqCallback return any negative status. + * @details Contains data that is required by the GPIO interrupt test. *****************************************************************************/ -static status_t AwaitDataReady(s2pi_slave_t slave, uint32_t timeout_ms) -{ - ltc_t start; - Time_GetNow(&start); +typedef struct gpio_data_t { + /* The S2PI slave parameter passed to the S2PI HAL functions. */ + s2pi_slave_t Slave; - while (S2PI_ReadIrqPin(slave)) { - if (Time_CheckTimeoutMSec(&start, timeout_ms)) { - PX4_INFO_RAW("SPI interrupt test failed! The S2PI_ReadIrqPin did not " - "determine an pending interrupt within %u ms.", (uint)timeout_ms); - return ERROR_TIMEOUT; - } + /* The callback status. */ + volatile status_t Status; + + /* The GPIO timeout in milliseconds. */ + uint32_t Timeout_ms; + + /* A counter to determine how often the callback is invoked. */ + volatile uint32_t CallbackInvoked; + + /* The return value of the #S2PI_ReadIrqPin function. */ + volatile uint32_t ReadIrqPinValue; + +} gpio_data_t; + +/*!*************************************************************************** + * @brief The IRQ callback dedicated to the #GpioInterruptTest. + * + * @details The callback is invoked by the API when the device GPIO IRQ is + * pending after a measurement has been executed and data is ready to + * be read from the device. + * + * @param param The abstract pointer to the boolean value that determines if + * the callback is invoked. + *****************************************************************************/ +static void GPIO_Callback(void *param) +{ + if (param == NULL) { + error_log("GPIO interrupt test failed: callback parameter \"param\" was NULL!"); + return; } + gpio_data_t *data = (gpio_data_t *)param; + data->CallbackInvoked = 1; +} + +/*!*************************************************************************** + * @brief The SPI transfer callback dedicated to the #GpioInterruptTest. + * + * @details The callback is invoked by the S2PI layer when the SPI transfer + * finished IRQ is invoked. The callback is used to simulate a + * deferred GPIO interrupt by locking the interrupts until the + * #S2PI_ReadIrqPin detects an GPIO interrupt pending state and + * returns 0. + * + * @param status The status of the SPI transfer. + * @param param The abstract pointer to the boolean value that determines if + * the callback is invoked. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_FAIL if the \p param parameter is NULL. + * - #ERROR_TIMEOUT if the #S2PI_ReadIrqPin does not return 0 after + * a specified time (check also timer HAL implementation). + * - The S2PI layer error code that may be received from the S2PI + * module via the \p status parameter. + *****************************************************************************/ +static status_t GPIO_SPI_Callback(status_t status, void *param) +{ + IRQ_LOCK(); // prevents GPIO interrupt to preempt if set to higher priority. + + if (param == NULL) { + IRQ_UNLOCK(); + error_log("GPIO interrupt test failed: callback parameter \"param\" was NULL!"); + return ERROR_FAIL; + } + + gpio_data_t *data = (gpio_data_t *)param; + + if (status != STATUS_OK) { + IRQ_UNLOCK(); + error_log("GPIO interrupt test failed: callback parameter \"status\" was %d!", + status); + data->Status = status; + return status; + } + + /* The S2PI_ReadIrqPin must correctly return the GPIO IRQ state if the GPIO + * interrupt is pending but deferred due to any higher priority or critical + * sections. Therefore, the SPI callback with the #IRQ_LOCK/#IRQ_UNLOCK is + * used to delay the GPIO callback and test the #S2PI_ReadIrqPin function. + * + * The purpose is to simulate a delayed GPIO interrupt that can in the + * production code happen due to any higher priority interrupts (such as + * the SPI interrupt in this test). In those cases, the API relies on the + * #S2PI_ReadIrqPin method to obtain if the device has finished in time and + * the interrupt is already pending. Otherwise, it would fail with an + * timeout due to the deferred GPIO interrupt callback event. */ + + ltc_t start; + Time_GetNow(&start); + data->ReadIrqPinValue = S2PI_ReadIrqPin(data->Slave); + + while (data->ReadIrqPinValue) { + if (Time_CheckTimeoutMSec(&start, data->Timeout_ms)) { + IRQ_UNLOCK(); + error_log("GPIO interrupt test failed! The IRQ pin did not assert " + "to low state when reading from the IRQ callback. " + "Elapsed %d ms.", data->Timeout_ms); + data->Status = ERROR_TIMEOUT; + return ERROR_TIMEOUT; + } + + data->ReadIrqPinValue = S2PI_ReadIrqPin(data->Slave); + } + + IRQ_UNLOCK(); + data->Status = STATUS_OK; return STATUS_OK; } /*!*************************************************************************** - * @brief SPI Interrupt Test for S2PI HAL Implementation. + * @brief SPI Interrupt Test for S2PI HAL Implementation. * - * @details This test verifies the correct implementation of the device - * integration finished interrupt callback. Therefore it configures - * the device with a minimal setup to run a pseudo measurement that - * does not emit any laser light. + * @details This test verifies the correct implementation of the device + * integration finished interrupt callback, a.k.a. the GPIO interrupt. + * Therefore it configures the device with a minimal setup to run a + * pseudo measurement that does not emit any laser light but triggers + * an GPIO interrupt once finished. * - * Note that this test does verify the GPIO interrupt that occurs - * whenever the device has finished the integration/measurement and - * new data is waiting to be read from the device. This does not test - * the interrupt that is triggered when the SPI transfer has finished. + * The data ready interrupt implies two S2PI layer functions that + * are tested in this test: The #S2PI_SetIrqCallback function installs + * a callback function that is invoked whenever the IRQ occurs and + * the #S2PI_ReadIrqPin function to obtain the pending interrupt state. * - * The data ready interrupt implies two S2PI layer functions that - * are tested in this test: The #S2PI_SetIrqCallback function installs - * a callback function that is invoked whenever the IRQ occurs. - * The IRQ can be delayed due to higher priority task, e.g. from the - * user code. It is essential for the laser safety timeout algorithm - * to determine the device ready signal as fast as possible, another - * method is implemented to read if the IRQ is pending but the - * callback has not been reset yet. This is what the #S2PI_ReadIrqPin - * function is for. + * The IRQ can be delayed due to higher priority task, e.g. from the + * user code. It is essential for the laser safety timeout algorithm + * to determine the device ready signal as fast as possible. Thus a + * method is required to obtain if the IRQ is currently pending but + * the callback has not been invoked yet. This is what the + * #S2PI_ReadIrqPin function is for. Note that the #S2PI_ReadIrqPin + * must return 0 if not interrupt is pending and 1 else. Just like + * the IRQ pin is active low. * + * The test simulate a delayed GPIO interrupt by locking the interrupts + * until the #S2PI_ReadIrqPin detects an GPIO interrupt pending state + * and returns 0. This is done by the #GPIO_SPI_Callback function. + * + * Note that this test does verify the GPIO interrupt that occurs + * whenever the device has finished the integration/measurement and + * new data is waiting to be read from the device. This does not test + * the interrupt that is triggered when the SPI transfer has finished. * * @warning The test assumes the device is in a fresh power on state and no - * additional reset is required. If the test fail, one may want to - * power cycle the device and try again. + * additional reset is required. If the test fail, one may want to + * power cycle the device and try again. * - * @warning The test utilizes already the timer HAL in order to implement a - * rudimentary timeout. However, at this time, only some basic - * plausibility checks are performed on the timer HAL. I.e. if there - * is an issue in the time HAL, e.g. too fast or too slow time - * counting, the test may fail with an #ERROR_TIMEOUT. In this case, - * one also needs to verify the timer HAL, especially the - * #Timer_GetCounterValue function. + * @warning The test locks the interrupts for a quite long period of time in + * order to simulate a delayed GPIO interrupt. This is not a good + * practice in production code. However, it is required to test the + * #S2PI_ReadIrqPin function. Please be aware of that when you run + * this test. * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @warning The test utilizes already the timer HAL in order to implement a + * rudimentary timeout. However, at this time, only some basic + * plausibility checks are performed on the timer HAL. I.e. if there + * is an issue in the time HAL, e.g. too fast or too slow time + * counting, the test may fail with an #ERROR_TIMEOUT. In this case, + * one also needs to verify the timer HAL, especially the + * #Timer_GetCounterValue function. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_TIMEOUT if either the SPI operation did not finished - * or the IRQ was not detected within a specified time (check also - * timer HAL implementation). - * - #ERROR_FAIL if the IRQ pin readout failed and the no or invalid - * interrupt was detected. - * - The S2PI layer error code if #S2PI_TransferFrame, #S2PI_GetStatus - * or #S2PI_SetIrqCallback return any negative status. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if either the SPI operation did not finished + * or the IRQ was not detected within a specified time (check also + * timer HAL implementation). + * - #ERROR_FAIL if the IRQ pin readout failed and the no or invalid + * interrupt was detected. + * - The S2PI layer error code if #S2PI_TransferFrame, #S2PI_GetStatus + * or #S2PI_SetIrqCallback return any negative status. *****************************************************************************/ -static status_t SpiInterruptTest(s2pi_slave_t slave) +static status_t GpioInterruptTest(s2pi_slave_t slave) { /* Test parameter configuration: *****************************************/ const uint32_t timeout_ms = 300; // timeout for measurement, might be increased.. /*************************************************************************/ - /* Install IRQ callback. */ - volatile bool isDataReady = false; - status_t status = S2PI_SetIrqCallback(slave, DataReadyCallback, (void *)&isDataReady); + gpio_data_t data = { .Slave = slave, + .Status = ERROR_FAIL, + .Timeout_ms = timeout_ms, + .ReadIrqPinValue = 12345, + .CallbackInvoked = 0 + }; - if (status < STATUS_OK) { - PX4_INFO_RAW("SPI interrupt test failed! The call to S2PI_SetIrqCallback " - "yielded error code: %d", (int)status); + /* Install IRQ callback. */ + status_t status = S2PI_SetIrqCallback(slave, GPIO_Callback, &data); + + if (status != STATUS_OK) { + error_log("GPIO interrupt test failed! The call to S2PI_SetIrqCallback " + "yielded error code: %d", status); + return status; + } + + /* Setup Device. */ + status = ConfigureDevice(slave, 0); + + if (status != STATUS_OK) { + error_log("GPIO interrupt test failed!"); return status; } /* Check if IRQ is not yet pending. */ if (S2PI_ReadIrqPin(slave) == 0) { - PX4_INFO_RAW("SPI interrupt test failed! The S2PI_ReadIrqPin did " - "return 0 but no interrupt is pending since no " - "measurements are executed yet!"); + error_log("GPIO interrupt test failed! The S2PI_ReadIrqPin did " + "return 0 but no interrupt is pending since no " + "measurements are executed yet!"); return ERROR_FAIL; }; - /* Setup Device. */ - status = ConfigureDevice(slave, 0); - - if (status < STATUS_OK) { - PX4_INFO_RAW("SPI interrupt test failed!"); - return status; - } - /* Trigger Measurement. */ - status = TriggerMeasurement(slave, 0); + status = TriggerMeasurement(slave, 0, GPIO_SPI_Callback, &data); - if (status < STATUS_OK) { - PX4_INFO_RAW("SPI interrupt test failed!"); - return status; - } - - ltc_t start; - Time_GetNow(&start); - - /* Wait for Interrupt using the S2PI_ReadIrqPin method. */ - status = AwaitDataReady(slave, timeout_ms); - - if (status < STATUS_OK) { - PX4_INFO_RAW("SPI interrupt test failed!"); + if (status != STATUS_OK) { + error_log("GPIO interrupt test failed!"); return status; } /* Wait for Interrupt using the callback method. */ - while (!isDataReady) { + ltc_t start; + Time_GetNow(&start); + + while (!data.CallbackInvoked) { if (Time_CheckTimeoutMSec(&start, timeout_ms)) { - PX4_INFO_RAW("SPI interrupt test failed! The IRQ callback was not " - "invoked within %u ms.", (uint)timeout_ms); + error_log("GPIO interrupt test failed! The IRQ callback was not " + "invoked within %d ms.", timeout_ms); return ERROR_TIMEOUT; } } + /* Verify ... */ + if (data.Status != STATUS_OK) { + error_log("GPIO interrupt test failed! The SPI IRQ callback yielded " + "an error status: %d (expected 0)", data.Status); + return ERROR_FAIL; + } + + if (data.ReadIrqPinValue != 0) { + error_log("GPIO interrupt test failed! The IRQ pin returned " + "the wrong value: %d (expected 0)", data.ReadIrqPinValue); + return ERROR_FAIL; + } + /* Remove callback. */ status = S2PI_SetIrqCallback(slave, 0, 0); - if (status < STATUS_OK) { - PX4_INFO_RAW("SPI interrupt test failed! The call to S2PI_SetIrqCallback " - "with null pointers yielded error code: %d", (int)status); + if (status != STATUS_OK) { + error_log("GPIO interrupt test failed! The call to S2PI_SetIrqCallback " + "with null pointers yielded error code: %d", status); return status; } @@ -826,28 +1105,28 @@ static status_t SpiInterruptTest(s2pi_slave_t slave) } /*!*************************************************************************** - * @brief Reads the EEPROM bytewise and applies Hamming weight. - * @details The EEPROM bytes are consecutevly read from the device via GPIO mode. - * The #EEPROM_Read function is an internal API function that enables - * the GPIO mode from the S2PI module and reads the data via a software - * bit-banging protocol. Finally it disables the GPIO mode and returns - * to SPI mode. + * @brief Reads the EEPROM byte-wise and applies Hamming weight. + * @details The EEPROM bytes are consecutively read from the device via GPIO mode. + * The EEPROM_Read function is an internal API function that enables + * the GPIO mode from the S2PI module and reads the data via a software + * bit-banging protocol. Finally it disables the GPIO mode and returns + * to SPI mode. * - * The calls to S2PI HAL module is as follows: - * 1. S2PI_CaptureGpioControl - * 2. multiple calls to S2PI_WriteGpioPin and S2PI_ReadGpioPin - * 3. S2PI_ReleaseGpioControl + * The calls to S2PI HAL module is as follows: + * 1. S2PI_CaptureGpioControl + * 2. multiple calls to S2PI_WriteGpioPin and S2PI_ReadGpioPin + * 3. S2PI_ReleaseGpioControl * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. - * @param eeprom The 16 byte array to be filled with EEPROM data. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @param eeprom The 16 byte array to be filled with EEPROM data. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #STATUS_ARGUS_EEPROM_BIT_ERROR if the Hamming weight fails. - * interrupt was detected. - * - The S2PI layer error code if #S2PI_CaptureGpioControl, - * #S2PI_ReleaseGpioControl, #S2PI_WriteGpioPin or - * #S2PI_ReadGpioPin return any negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #STATUS_ARGUS_EEPROM_BIT_ERROR if the Hamming weight fails. + * interrupt was detected. + * - The S2PI layer error code if #S2PI_CaptureGpioControl, + * #S2PI_ReleaseGpioControl, #S2PI_WriteGpioPin or + * #S2PI_ReadGpioPin return any negative status. *****************************************************************************/ static status_t ReadEEPROM(s2pi_slave_t slave, uint8_t *eeprom) { @@ -855,9 +1134,9 @@ static status_t ReadEEPROM(s2pi_slave_t slave, uint8_t *eeprom) uint8_t d1[] = { 0x12, 0x00, 0x4B }; status_t status = SPITransferSync(slave, d1, sizeof(d1)); - if (status < STATUS_OK) { - PX4_INFO_RAW("EEPROM readout failed (enable EEPROM), " - "error code: %d", (int)status); + if (status != STATUS_OK) { + error_log("EEPROM readout failed (enable EEPROM), " + "error code: %d", status); return status; } @@ -868,8 +1147,8 @@ static status_t ReadEEPROM(s2pi_slave_t slave, uint8_t *eeprom) status = EEPROM_Read(slave, address, &data[address]); if (status != STATUS_OK) { - PX4_INFO_RAW("EEPROM readout failed @ address 0x%02x, " - "error code: %d!", address, (int)status); + error_log("EEPROM readout failed @ address 0x%02x, " + "error code: %d!", address, status); return status; } } @@ -878,9 +1157,9 @@ static status_t ReadEEPROM(s2pi_slave_t slave, uint8_t *eeprom) uint8_t d2[] = { 0x12, 0x00, 0x2B }; status = SPITransferSync(slave, d2, sizeof(d2)); - if (status < STATUS_OK) { - PX4_INFO_RAW("EEPROM readout failed (enable EEPROM), " - "error code: %d", (int)status); + if (status != STATUS_OK) { + error_log("EEPROM readout failed (enable EEPROM), " + "error code: %d", status); return status; } @@ -888,8 +1167,8 @@ static status_t ReadEEPROM(s2pi_slave_t slave, uint8_t *eeprom) uint8_t err = hamming_decode(data, eeprom); if (err != 0) { - PX4_INFO_RAW("EEPROM readout failed! Failed to decoding " - "Hamming weight (error: %d)!", err); + error_log("EEPROM readout failed! Failed to decoding " + "Hamming weight (Hamming parity error: %d)!", err); return STATUS_ARGUS_EEPROM_BIT_ERROR; } @@ -900,30 +1179,30 @@ static status_t ReadEEPROM(s2pi_slave_t slave, uint8_t *eeprom) } /*!*************************************************************************** - * @brief GPIO Mode Test for S2PI HAL Implementation. + * @brief GPIO Mode Test for S2PI HAL Implementation. * * @details This test verifies the GPIO mode of the S2PI HAL module. This is - * done by leveraging the EEPROM readout sequence that accesses the - * devices EEPROM via a software protocol that depends on the GPIO - * mode. + * done by leveraging the EEPROM readout sequence that accesses the + * devices EEPROM via a software protocol that depends on the GPIO + * mode. * - * This the requires several steps, most of them are already verified - * in previous tests: - * - Basic device configuration and enable EEPROM. - * - Read EERPOM via GPIO mode and apply Hamming weight - * - Repeat several times (to eliminate random readout issues). - * - Decode the EEPROM (using EEPROM_Decode in argus_cal_eeprom.c) - * - Check if Module Number and Chip ID is not 0 + * This the requires several steps, most of them are already verified + * in previous tests: + * - Basic device configuration and enable EEPROM. + * - Read EEPROM via GPIO mode and apply Hamming weight + * - Repeat several times (to eliminate random readout issues). + * - Decode the EEPROM (using EEPROM_Decode in argus_cal_eeprom.c) + * - Check if Module Number and Chip ID is not 0 * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_FAIL if the GPIO test fails. - * - #STATUS_ARGUS_EEPROM_BIT_ERROR if the Hamming weight fails. - * - The S2PI layer error code if #S2PI_CaptureGpioControl, - * #S2PI_ReleaseGpioControl, #S2PI_WriteGpioPin or - * #S2PI_ReadGpioPin return any negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_FAIL if the GPIO test fails. + * - #STATUS_ARGUS_EEPROM_BIT_ERROR if the Hamming weight fails. + * - The S2PI layer error code if #S2PI_CaptureGpioControl, + * #S2PI_ReleaseGpioControl, #S2PI_WriteGpioPin or + * #S2PI_ReadGpioPin return any negative status. *****************************************************************************/ static status_t GpioModeTest(s2pi_slave_t slave) { @@ -934,66 +1213,66 @@ static status_t GpioModeTest(s2pi_slave_t slave) status_t status = ReadEEPROM(slave, eeprom1); - if (status < STATUS_OK) { - PX4_INFO_RAW("GPIO mode test failed (1st attempt)!"); + if (status != STATUS_OK) { + error_log("GPIO mode test failed (1st attempt)!"); return status; } status = ReadEEPROM(slave, eeprom2); - if (status < STATUS_OK) { - PX4_INFO_RAW("GPIO mode test failed (2nd attempt)!"); + if (status != STATUS_OK) { + error_log("GPIO mode test failed (2nd attempt)!"); return status; } status = ReadEEPROM(slave, eeprom3); - if (status < STATUS_OK) { - PX4_INFO_RAW("GPIO mode test failed (3rd attempt)!"); + if (status != STATUS_OK) { + error_log("GPIO mode test failed (3rd attempt)!"); return status; } /* Verify EEPROM data. */ if ((memcmp(eeprom1, eeprom2, 16) != 0) || (memcmp(eeprom1, eeprom3, 16) != 0)) { - PX4_INFO_RAW("GPIO Mode test failed (data comparison)!\n" - "The data from 3 distinct EEPROM readout does not match!"); + error_log("GPIO Mode test failed (data comparison)!\n" + "The data from 3 distinct EEPROM readout does not match!"); return ERROR_FAIL; } /* Check EEPROM data for reasonable chip and module number (i.e. not 0) */ uint32_t chipID = EEPROM_ReadChipId(eeprom1); - argus_module_version_t module = EEPROM_ReadModule(eeprom1); + uint8_t module = EEPROM_ReadModule(eeprom1); if (chipID == 0 || module == 0) { - PX4_INFO_RAW("GPIO Mode test failed (data verification)!\n" - "Invalid EEPROM data: Module = %d; Chip ID = %u!", module, (uint)chipID); + error_log("GPIO Mode test failed (data verification)!\n" + "Invalid EEPROM data: Module = %d; Chip ID = %d!", module, chipID); return ERROR_FAIL; } - PX4_INFO_RAW("EEPROM Readout succeeded!\n"); - PX4_INFO_RAW("- Module: %d\n", module); - PX4_INFO_RAW("- Device ID: %u\n", (uint)chipID); + print("EEPROM Readout succeeded!\n"); + print("- Module: %d\n", module); + print("- Device ID: %d\n", chipID); return STATUS_OK; } /*!*************************************************************************** - * @brief Reads the RCO_TRIM value from the devices EEPROM. + * @brief Reads the RCO_TRIM value from the devices EEPROM. * * @details The function reads the devices EEPROM via GPIO mode and extracts - * the RCO_TRIM value from the EEPROM map. + * the RCO_TRIM value from the EEPROM map. * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. - * @param rcotrim The read RCO_TRIM value will be returned via this pointer. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @param rcotrim The read RCO_TRIM value will be returned via this pointer. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #STATUS_ARGUS_EEPROM_BIT_ERROR if the Hamming weight fails. - * - #ERROR_ARGUS_UNKNOWN_MODULE if the EEPROM module number is invalid. - * - The S2PI layer error code if #S2PI_CaptureGpioControl, - * #S2PI_ReleaseGpioControl, #S2PI_WriteGpioPin or - * #S2PI_ReadGpioPin return any negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #STATUS_ARGUS_EEPROM_BIT_ERROR if the Hamming weight fails. + * - #ERROR_ARGUS_UNKNOWN_MODULE if the EEPROM module number is invalid. + * - The S2PI layer error code if #S2PI_CaptureGpioControl, + * #S2PI_ReleaseGpioControl, #S2PI_WriteGpioPin or + * #S2PI_ReadGpioPin return any negative status. *****************************************************************************/ static status_t ReadRcoTrim(s2pi_slave_t slave, int8_t *rcotrim) { @@ -1003,25 +1282,15 @@ static status_t ReadRcoTrim(s2pi_slave_t slave, int8_t *rcotrim) if (status != STATUS_OK) { return status; } - argus_module_version_t module = EEPROM_ReadModule(eeprom); - - switch (module) { - case AFBR_S50MV85G_V1: - case AFBR_S50MV85G_V2: - case AFBR_S50MV85G_V3: - case AFBR_S50LV85D_V1: - case AFBR_S50MV68B_V1: - case AFBR_S50MV85I_V1: - case AFBR_S50SV85K_V1: + uint8_t module = EEPROM_ReadModule(eeprom); + if (module > 0 && module < 8) { /* Read RCO Trim Value from EEPROM Map 1/2/3: */ *rcotrim = ((int8_t) eeprom[0]) >> 3; - break; - case MODULE_NONE: /* Uncalibrated module; use all 0 data. */ - default: - - PX4_INFO_RAW("EEPROM Readout failed! Unknown module number: %d", module); + } else { + /* Uncalibrated module; use all 0 data. */ + error_log("EEPROM Readout failed! Unknown module number: %d", module); return ERROR_ARGUS_UNKNOWN_MODULE; } @@ -1029,112 +1298,151 @@ static status_t ReadRcoTrim(s2pi_slave_t slave, int8_t *rcotrim) } /*!*************************************************************************** - * @brief Triggers a measurement on the device and waits for the data ready - * interrupt. + * @brief Callback function for the data ready interrupt. + * + * @details The function is called by the S2PI layer when the data ready + * interrupt is pending. The function sets the \p param to + * #STATUS_IDLE. + * + * @param param The parameter passed to the #S2PI_SetIrqCallback function as + * an abstract pointer to an #status_t type. + *****************************************************************************/ +static void MeasurementCallback(void *param) +{ + *(status_t *) param = STATUS_IDLE; +} + +/*!*************************************************************************** + * @brief Triggers a measurement on the device and waits for the data ready + * interrupt. * * @details The function triggers a measurement cycle on the device and waits - * until the measurement has been finished. A \p sample count can be - * specified to setup individual number of digital averaging. + * until the measurement has been finished. A \p sample count can be + * specified to setup individual number of digital averaging. * - * @warning The test utilizes already the timer HAL in order to implement a - * rudimentary timeout. However, at this time, only some basic - * plausibility checks are performed on the timer HAL. I.e. if there - * is an issue in the time HAL, e.g. too fast or too slow time - * counting, the test may fail with an #ERROR_TIMEOUT. In this case, - * one also needs to verify the timer HAL, especially the - * #Timer_GetCounterValue function. + * @warning The test utilizes already the timer HAL in order to implement a + * rudimentary timeout. However, at this time, only some basic + * plausibility checks are performed on the timer HAL. I.e. if there + * is an issue in the time HAL, e.g. too fast or too slow time + * counting, the test may fail with an #ERROR_TIMEOUT. In this case, + * one also needs to verify the timer HAL, especially the + * #Timer_GetCounterValue function. * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. - * @param samples The specified number of averaging samples for the measurement. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @param samples The specified number of averaging samples for the measurement. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_TIMEOUT if either the SPI operation did not finished - * or the IRQ was not detected within a specified time (check also - * timer HAL implementation). - * - The S2PI layer error code if #S2PI_TransferFrame, #S2PI_GetStatus - * or #S2PI_SetIrqCallback return any negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if either the SPI operation did not finished + * or the IRQ was not detected within a specified time (check also + * timer HAL implementation). + * - The S2PI layer error code if #S2PI_TransferFrame, #S2PI_GetStatus + * or #S2PI_SetIrqCallback return any negative status. *****************************************************************************/ static status_t RunMeasurement(s2pi_slave_t slave, uint16_t samples) { - status_t status = TriggerMeasurement(slave, samples); + /* Test parameter configuration: *****************************************/ + const uint32_t timeout_ms = 300; // The transfer timeout in ms. + /*************************************************************************/ - if (status < STATUS_OK) { - PX4_INFO_RAW("Speed test failed!\n" - "Call to TransferFrame returned code: %d", - (int)status); + volatile status_t callbackStatus = STATUS_BUSY; + + status_t status = S2PI_SetIrqCallback(slave, MeasurementCallback, (void *)&callbackStatus); + + if (status != STATUS_OK) { + error_log("Failed to run a measurement!\n" + "Call to SetIrqCallback returned code: %d", status); + return status; + } + + status = TriggerMeasurement(slave, samples, 0, 0); + + if (status != STATUS_OK) { + error_log("Failed to run a measurement!\n" + "Call to TransferFrame returned code: %d", status); return status; } /* Wait until the transfer is finished using a timeout. */ - status = AwaitDataReady(slave, 300); - if (status < STATUS_OK) { - PX4_INFO_RAW("Speed test failed!\n" - "SPI Read IRQ pin didn't raised, timeout activated at 200ms, error code: %d", - (int)status); - return status; + ltc_t start; + Time_GetNow(&start); + + while (callbackStatus == STATUS_BUSY) { + if (Time_CheckTimeoutMSec(&start, timeout_ms)) { + error_log("Failed to run a measurement!\n" + "Timeout occurred while waiting for the SPI interrupt (%d ms).", + timeout_ms); + return ERROR_TIMEOUT; + } } - return status; + if (callbackStatus != STATUS_OK) { + error_log("Failed to run a measurement!\n" + "The SPI callback yielded returned code: %d", + callbackStatus); + return callbackStatus; + } + + return STATUS_OK; } /*!*************************************************************************** - * @brief Test for Timer HAL Implementation by comparing timings to the device. + * @brief Test for Timer HAL Implementation by comparing timings to the device. * - * @details The test verifies the timer HAL implementation by comparing the - * timings to the AFBR-S50 device as a reference. - * Therefore several measurement are executed on the device, each with - * a different averaging sample count. The elapsed time increases - * linearly with the number of averaging samples. In order to remove - * the time for software/setup, a linear regression fit is applied to - * the measurement results and only the slope is considered for the - * result. A delta of 102.4 microseconds per sample is expected. - * If the measured delta per sample is within an specified error range, - * the timer implementation is considered correct. + * @details The test verifies the timer HAL implementation by comparing the + * timings to the AFBR-S50 device as a reference. + * Therefore several measurement are executed on the device, each with + * a different averaging sample count. The elapsed time increases + * linearly with the number of averaging samples. In order to remove + * the time for software/setup, a linear regression fit is applied to + * the measurement results and only the slope is considered for the + * result. A delta of 102.4 microseconds per sample is expected. + * If the measured delta per sample is within an specified error range, + * the timer implementation is considered correct. * - * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_FAIL if the timer test fails. - * - #STATUS_ARGUS_EEPROM_BIT_ERROR if the EEPROM Hamming weight fails. - * - #ERROR_ARGUS_UNKNOWN_MODULE if the EEPROM module number is invalid. - * - #ERROR_TIMEOUT if either the SPI operation did not finished - * or the IRQ was not detected within a specified time (check also - * timer HAL implementation). - * - The S2PI layer error code if #S2PI_TransferFrame, #S2PI_GetStatus, - * #S2PI_SetIrqCallback, #S2PI_CaptureGpioControl, - * #S2PI_ReleaseGpioControl, #S2PI_WriteGpioPin or #S2PI_ReadGpioPin - * return any negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_FAIL if the timer test fails. + * - #STATUS_ARGUS_EEPROM_BIT_ERROR if the EEPROM Hamming weight fails. + * - #ERROR_ARGUS_UNKNOWN_MODULE if the EEPROM module number is invalid. + * - #ERROR_TIMEOUT if either the SPI operation did not finished + * or the IRQ was not detected within a specified time (check also + * timer HAL implementation). + * - The S2PI layer error code if #S2PI_TransferFrame, #S2PI_GetStatus, + * #S2PI_SetIrqCallback, #S2PI_CaptureGpioControl, + * #S2PI_ReleaseGpioControl, #S2PI_WriteGpioPin or #S2PI_ReadGpioPin + * return any negative status. *****************************************************************************/ static status_t TimerTest(s2pi_slave_t slave) { /* Test parameter configuration: *****************************************/ - const int8_t n = 10; // The number of measurements. - const uint32_t ds = 100; // The step size in averaging samples. - const float exp_slope = 102.4; // Expected slope is 102.4 µs / phase / sample - const float rel_slope_error = 3e-2; // Relative slope tolerance is 3%. + const int8_t n = 10; // The number of measurements. + const uint32_t ds = 100; // The step size in averaging samples. + const float exp_slope = 102.4f; // Expected slope is 102.4 µs / phase / sample + const float rel_slope_error = 3e-2f; // Relative slope tolerance is 3%. /*************************************************************************/ /* Read RCOTrim value from EEPROM*/ int8_t RcoTrim = 0; status_t status = ReadRcoTrim(slave, &RcoTrim); - if (status < STATUS_OK) { - PX4_INFO_RAW("Timer test failed!\n" - "EEPROM Read test returned code: %d", (int)status); + if (status != STATUS_OK) { + error_log("Timer test failed!\n" + "EEPROM Read test returned code: %d", status); return status; } - PX4_INFO_RAW("RCOTrim = %d\n", RcoTrim); + print("RCOTrim = %d\n", RcoTrim); /* Configure the device with calibrated RCO to 24MHz. */ status = ConfigureDevice(slave, RcoTrim); - if (status < STATUS_OK) { - PX4_INFO_RAW("Timer test failed!\n" - "Configuration test returned code: %d", (int)status); + if (status != STATUS_OK) { + error_log("Timer test failed!\n" + "Configuration test returned code: %d", status); return status; } @@ -1146,21 +1454,23 @@ static status_t TimerTest(s2pi_slave_t slave) float x2sum = 0; float xysum = 0; - PX4_INFO_RAW("+-------+---------+------------+\n"); - PX4_INFO_RAW("| count | samples | elapsed us |\n"); - PX4_INFO_RAW("+-------+---------+------------+\n"); + print("+-------+---------+------------+\n"); + print("| count | samples | elapsed us |\n"); + print("+-------+---------+------------+\n"); for (uint8_t i = 1; i <= n; ++i) { ltc_t start; Time_GetNow(&start); - int samples = ds * i; - status = RunMeasurement(slave, samples); + uint32_t samples = ds * i; + assert(samples < UINT16_MAX); - if (status < STATUS_OK) { - PX4_INFO_RAW("Timer test failed!\n" - "Run measurement returned code: %d", - (int)status); + status = RunMeasurement(slave, (uint16_t)samples); + + if (status != STATUS_OK) { + error_log("Timer test failed!\n" + "Run measurement returned code: %d", + status); return status; } @@ -1168,30 +1478,30 @@ static status_t TimerTest(s2pi_slave_t slave) xsum += (float) samples; ysum += (float) elapsed_usec; - x2sum += (float) samples * samples; - xysum += (float) samples * elapsed_usec; + x2sum += (float) samples * (float) samples; + xysum += (float) samples * (float) elapsed_usec; - PX4_INFO_RAW("| %5d | %7d | %10d |\n", i, samples, (uint)elapsed_usec); + print("| %5d | %7d | %10d |\n", i, samples, elapsed_usec); } - PX4_INFO_RAW("+-------+---------+------------+\n"); + print("+-------+---------+------------+\n"); const float slope = (n * xysum - xsum * ysum) / (n * x2sum - xsum * xsum); const float intercept = (ysum * x2sum - xsum * xysum) / (n * x2sum - xsum * xsum); - PX4_INFO_RAW("Linear Regression: y(x) = %dE-7 sec * x + %dE-7 sec\n", - (int)(10 * slope), (int)(10 * intercept)); + print("Linear Regression: y(x) = %dE-7 sec * x + %dE-7 sec\n", + (int)(10 * slope), (int)(10 * intercept)); /* Check the error of the slope. */ const float max_slope = exp_slope * (1.f + rel_slope_error); const float min_slope = exp_slope * (1.f - rel_slope_error); if (slope > max_slope || slope < min_slope) { - PX4_INFO_RAW("Time test failed!\n" - "The measured time slope does not match the expected value! " - "(actual: %dE-7, expected: %dE-7, min: %dE-7, max: %dE-7)\n", - (int)(10 * slope), (int)(10 * exp_slope), - (int)(10 * min_slope), (int)(10 * max_slope)); + error_log("Time test failed!\n" + "The measured time slope does not match the expected value! " + "(actual: %dE-7, expected: %dE-7, min: %dE-7, max: %dE-7)\n", + (int)(10 * slope), (int)(10 * exp_slope), + (int)(10 * min_slope), (int)(10 * max_slope)); return ERROR_FAIL; } @@ -1200,11 +1510,11 @@ static status_t TimerTest(s2pi_slave_t slave) /*!*************************************************************************** - * @brief Data structure for the PIT test. + * @brief Data structure for the PIT test. * - * @details Contains data that is required by the PIT timer test. + * @details Contains data that is required by the PIT timer test. *****************************************************************************/ -typedef struct { +typedef struct pit_data_t { /*! The number of PIT callback events. */ volatile uint32_t n; @@ -1216,90 +1526,113 @@ typedef struct { } pit_data_t; - - /*!*************************************************************************** - * @brief Callback function invoked by the PIT. + * @brief Callback function invoked by the PIT. * - * @details The function that is invoked every time a specified interval elapses. - * An abstract parameter is passed to the function whenever it is called. + * @details The function that is invoked every time a specified interval elapses. + * An abstract parameter is passed to the function whenever it is called. * - * This implementation collects callback time stamps and counts the - * number of callback events using the abstract parameter. + * This implementation collects callback time stamps and counts the + * number of callback events using the abstract parameter. * - * @param param An abstract parameter to be passed to the callback. This is - * also the identifier of the given interval. + * @param param An abstract parameter to be passed to the callback. This is + * also the identifier of the given interval. *****************************************************************************/ static void PIT_Callback(void *param) { - pit_data_t *data = (pit_data_t *) param; - - if (data->n == 0) { - Time_GetNow(&data->t_first); - data->t_last = data->t_first; + if (param == NULL) { + error_log("PIT interrupt test failed: callback parameter \"param\" was NULL!"); } else { - Time_GetNow(&data->t_last); - } + pit_data_t *data = (pit_data_t *)param; - data->n++; + if (data->n == 0) { + Time_GetNow(&data->t_first); + data->t_last = data->t_first; + + } else { + Time_GetNow(&data->t_last); + } + + data->n++; + } } /*!*************************************************************************** - * @brief Executes a PIT measurement and verifies the callback interval. + * @brief Executes a PIT measurement and verifies the callback interval. * * @details The function configures the PIT with a given interval and waits - * several callback events to happen. In each callback event, the - * elapsed time is measured and the number of calls are counted. - * Finally, the average interrupt period is compared with the - * lifetime timer that has been already verified in a previous test - * (see #Timer_Test). + * several callback events to happen. In each callback event, the + * elapsed time is measured and the number of calls are counted. + * Finally, the average interrupt period is compared with the + * lifetime timer that has been already verified in a previous test + * (see #TimerTest). The time until the first interrupt event is also + * verified. * - * @param exp_dt_us The expected timer interval in microseconds. - * @param n The number of PIT events to await. + * @param exp_dt_us The expected timer interval in microseconds. + * @param n The number of PIT events to await. * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_FAIL if the measured interval does not match the - * expectations or the PIT was not disabled properly. - * - #ERROR_TIMEOUT if either the PIT events do not occur within the - * expected time. - * - The PIT layer error code if #Timer_SetInterval return any - * negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_FAIL if the measured interval does not match the + * expectations or the PIT was not disabled properly. + * - #ERROR_TIMEOUT if either the PIT events do not occur within the + * expected time. + * - The PIT layer error code if #Timer_SetInterval return any + * negative status. *****************************************************************************/ static status_t RunPITTest(uint32_t exp_dt_us, uint32_t n) { /* Test parameter configuration: *****************************************/ - const float rel_dt_error = 1e-3; // Relative timer interval tolerance is 0.1%. - const float abs_dt_error = 1.0; // Absolute timer interval tolerance is 1us. + const float rel_dt_error = 5e-3f; // Relative timer interval tolerance: 0.5 %. + const float abs_dt_error = 5.0f; // Absolute timer interval tolerance: 5.0 us. /*************************************************************************/ - float dt = exp_dt_us * rel_dt_error; + float dt = (float) exp_dt_us * rel_dt_error; if (dt < abs_dt_error) { dt = abs_dt_error; } - const float max_dt = exp_dt_us + dt; - const float min_dt = exp_dt_us - dt; + const float max_dt = (float) exp_dt_us + dt; + const float min_dt = (float) exp_dt_us - dt; + + if (dt < abs_dt_error * 3) { dt = abs_dt_error * 3; } + + const float t_first_max = (float) exp_dt_us + dt * 5; // use 5x tolerance for + const float t_first_min = (float) exp_dt_us - dt * 5; // the first interval /*************************************************************************/ + print("Run PIT Test (w/ %d us interval):\n" + " - expected event count: %d\n" + " - expected interval: %d us, min: %d us, max: %d us\n" + " - expected first event: %d us, min: %d us, max: %d us\n", + exp_dt_us, n, exp_dt_us, (int)min_dt, (int)max_dt, + exp_dt_us, (int)t_first_min, (int)t_first_max); + /* Setup the PIT callback with specified interval. */ pit_data_t data = { 0 }; status_t status = Timer_SetInterval(exp_dt_us, &data); - if (status < STATUS_OK) { - PX4_INFO_RAW("PIT test failed!\n" - "Timer_SetInterval returned status code: %d", (int)status); + if (status != STATUS_OK) { + error_log("PIT test failed!\n" + "Timer_SetInterval returned status code: %d", status); return status; } /* Wait until n PIT callback have been happened. */ - uint32_t timeout_us = (n + 1) * exp_dt_us; + const uint32_t timeout_us = (n + 1) * exp_dt_us; + ltc_t start; Time_GetNow(&start); while (data.n < n) { if (Time_CheckTimeoutUSec(&start, timeout_us)) { - PX4_INFO_RAW("PIT test failed!\n" - "Waiting for the PIT interrupt events yielded a timeout."); + const uint32_t elapsed_us = Time_GetElapsedUSec(&start); + const uint32_t t_first_us = Time_DiffUSec(&start, &data.t_first); + const uint32_t t_last_us = Time_DiffUSec(&start, &data.t_last); + error_log("PIT test failed!\n" + "Waiting for the PIT interrupt events yielded a timeout.\n" + "Timeout: %d us; Elapsed: %d us (%d of %d events).\n" + "First event @ %d us, last event @ %d us", + timeout_us, elapsed_us, data.n, n, t_first_us, t_last_us); status = ERROR_TIMEOUT; break; } @@ -1309,59 +1642,71 @@ static status_t RunPITTest(uint32_t exp_dt_us, uint32_t n) /* Disable the PIT timer callback. */ status = Timer_SetInterval(0, &data); - if (status < STATUS_OK) { - PX4_INFO_RAW("PIT test failed!\n" - "Timer_SetInterval returned status code: %d", (int)status); + if (status != STATUS_OK) { + error_log("PIT test failed!\n" + "Timer_SetInterval returned status code: %d", status); } } if (status == STATUS_OK) { /* Check if PIT callback is not invoked any more. */ - timeout_us = 2 * exp_dt_us; - Time_GetNow(&start); - - while (!Time_CheckTimeoutUSec(&start, timeout_us)) { __asm("nop"); } + Time_DelayUSec(3 * exp_dt_us); if (data.n > n) { - PX4_INFO_RAW("PIT test failed!\n" - "Timer_SetInterval has been called after it was disabled."); + const uint32_t elapsed_us = Time_GetElapsedUSec(&start); + error_log("PIT test failed!\n" + "Timer_SetInterval has been called again after it was disabled\n" + "(within %d us; %d of %d events in total).", + elapsed_us, data.n, n); status = ERROR_FAIL; } } /* Verify the measured average timer interval. */ - const float act_dt_us = Time_DiffUSec(&data.t_first, &data.t_last) / (n - 1); + const float act_dt_us = Time_DiffUSec(&data.t_first, &data.t_last) / (float)(n - 1); + const uint32_t t_first_us = Time_DiffUSec(&start, &data.t_first); + const uint32_t t_last_us = Time_DiffUSec(&start, &data.t_last); - if (status == STATUS_OK && (act_dt_us > max_dt || act_dt_us < min_dt)) { - PX4_INFO_RAW("PIT test failed!\n" - "The measured timer interval does not match the expected value!\n"); + print(" - actual event count: %d\n" + " - actual interval: %d us\n" + " - actual first event: %d us\n" + " - actual last event: %d us\n\n", + data.n, (int)act_dt_us, t_first_us, t_last_us); + + if (status == STATUS_OK && (t_first_us > t_first_max || t_first_us < t_first_min)) { + error_log("PIT test failed!\n" + "The first timer event did not occur after the expected interval!"); status = ERROR_FAIL; } - PX4_INFO_RAW("PIT Test Results:\n" - " - event count: %u\n" - " - actual interval: %d us\n" - " - expected interval: %d us, min: %d us, max: %d us\n", - (uint)data.n, (int)act_dt_us, (uint)exp_dt_us, (int)min_dt, (int)max_dt); + if (status == STATUS_OK && (act_dt_us > max_dt || act_dt_us < min_dt)) { + error_log("PIT test failed!\n" + "The measured timer interval does not match the expected value!"); + status = ERROR_FAIL; + } + + print(" - test status: %d\n\n", status); return status; } /*!*************************************************************************** - * @brief Test for PIT HAL Implementation by comparing timings to the device. + * @brief Test for PIT HAL Implementation by comparing timings to the device. * - * @details The test verifies the timer HAL implementation by comparing the + * @details The test verifies the timer HAL implementation by comparing the + * period between the interrupts with the lifetime timer values + * that has been already verified in a previous test (see #TimerTest). * - * @return Returns the \link #status_t status\endlink: - * - #STATUS_OK on success. - * - #ERROR_NOT_IMPLEMENTED if the PIT functionality is not - * implemented and the test is skipped. - * - #ERROR_FAIL if the measured interval does not match the - * expectations or the PIT was not disabled properly. - * - #ERROR_TIMEOUT if either the PIT events do not occur within the - * expected time. - * - The PIT layer error code if #Timer_SetInterval or - * #Timer_SetCallback return any negative status. + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_NOT_IMPLEMENTED if the PIT functionality is not + * implemented and the test is skipped. + * - #ERROR_FAIL if the measured interval does not match the + * expectations or the PIT was not disabled properly. + * - #ERROR_TIMEOUT if either the PIT events do not occur within the + * expected time. + * - The PIT layer error code if #Timer_SetInterval or + * #Timer_SetCallback return any negative status. *****************************************************************************/ static status_t PITTest(void) { @@ -1369,31 +1714,595 @@ static status_t PITTest(void) if (status == ERROR_NOT_IMPLEMENTED) { return status; } - if (status < STATUS_OK) { - PX4_INFO_RAW("PIT test failed!\n" - "Timer_SetCallback returned status code: %d", (int)status); + if (status != STATUS_OK) { + error_log("PIT test failed!\n" + "Timer_SetCallback returned status code: %d", status); return status; } + status = RunPITTest(200000, 5); + + if (status != STATUS_OK) { return status; } + status = RunPITTest(10000, 10); - if (status < STATUS_OK) { return status; } + if (status != STATUS_OK) { return status; } - status = RunPITTest(333, 1000); + /* High Speed Test down to 1000 microseconds. If this fails, just print + * a message that very high frame rates might have issues. */ + status = RunPITTest(1000, 500); - if (status < STATUS_OK) { return status; } + if (status != STATUS_OK) { + print("WARNING: PIT test failed for 1000 us interval!\n" + " This is only critical if high frame rates (up to 1000 fps)\n" + " need to be achieved. Otherwise, the error can be safely ignored.\n"); + status = STATUS_IGNORE; // ignore + } - status = RunPITTest(100000, 5); + if (status == STATUS_OK) { // only run if previous test succeeded! + /* High Speed Test down to 333 microseconds. If this fails, just print + * a message that very high frame rates might have issues. */ + status = RunPITTest(333, 500); - if (status < STATUS_OK) { return status; } + if (status != STATUS_OK) { + print("WARNING: PIT test failed for 333 us interval!\n" + " This is only critical if very high frame rates (up to 3000 fps)\n" + " need to be achieved. Otherwise, the error can be safely ignored.\n"); + status = STATUS_IGNORE; // ignore + } + } status = Timer_SetCallback(0); - if (status < STATUS_OK) { - PX4_INFO_RAW("PIT test failed!\n" - "Timer_SetCallback to 0 returned status code: %d", (int)status); + if (status != STATUS_OK) { + error_log("PIT test failed!\n" + "Timer_SetCallback to 0 returned status code: %d", status); return status; } return STATUS_OK; } + +/*!*************************************************************************** + * @brief Data structure for the S2PI transfer from interrupt tests. + * + * @details Contains data that is required by the S2PI transfer from interrupt + * test. The data structure is passed to the corresponding interrupt + * callback functions. + *****************************************************************************/ +typedef struct spi_irq_data_t { + /*! The status of the interrupt callback function. */ + volatile status_t Status; + + /*! The S2PI slave parameter passed to the S2PI HAL functions. */ + s2pi_slave_t Slave; + + /*! The data buffer to be transferred from/to the device for testing purposes. */ + uint8_t Data[17U]; + + /*! Set to true when all SPI transfers are finished. */ + volatile bool Finished; + + /*! Set to true when the second SPI transfers is started. + The second transfer is used to read-back the previously set values. */ + volatile bool ReadBack; + +} spi_irq_data_t; + + +/*!*************************************************************************** + * @brief SPI interrupt callback function for the SPI transfer from IRQ test. + * + * @details The interrupt callback is invoked from the S2PI module upon + * finishing the SPI transfer. The callback is used by the + * #SpiTransferFromSpiInterrupt test to trigger the second SPI transfer + * from the interrupt callback context. + * + * @note The callback also utilizes the #print functionality. This requires + * a correct implementation of the corresponding function such that it + * can be invoked from the given interrupt context. This usually + * requires the underlying send (e.g. UART or USB send functions) to + * have higher priority that this interrupt in order to finished the + * print statement asynchronously. + * + * @param status The S2PI module status passed to the callback. + * @param param The abstract interrupt callback parameter. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_INVALID_ARGUMENT if the \p param is NULL. + * - The S2PI layer error code if any is passed to the callback function. + * - The S2PI layer error code if #S2PI_TransferFrame return any. + *****************************************************************************/ +static status_t SpiTransferFromSpiInterruptCallback(status_t status, void *param) +{ + if (param == NULL) { + error_log("SPI transfer from SPI interrupt test failed\n" + "callback parameter \"param\" was NULL!"); + return ERROR_INVALID_ARGUMENT; + } + + spi_irq_data_t *data = (spi_irq_data_t *) param; + + if (status != STATUS_OK) { + error_log("SPI transfer from SPI interrupt test failed:\n" + "callback received error! Error code: %d", status); + data->Status = status; + return status; + } + + if (!data->ReadBack) { + print("Invoking SPI transfer from SPI interrupt callback...\n"); + + /* Clear the laser pattern and read back previous values. */ + data->Data[0] = 0x04; // Laser Pattern Register Address + + for (uint8_t i = 1; i < 17U; ++i) { data->Data[i] = 0; } + + status = S2PI_TransferFrame(data->Slave, data->Data, data->Data, 17U, + SpiTransferFromSpiInterruptCallback, param); + + if (status != STATUS_OK) { + error_log("SPI transfer from SPI interrupt test failed:\n" + "Calling S2PI_TransferFrame from SPI interrupt " + "returned error code: %d", status); + data->Status = status; + return status; + } + + data->ReadBack = true; + + } else { + data->Finished = true; + } + + return STATUS_OK; +} + +/*!*************************************************************************** + * @brief SPI transfer from SPI interrupt callback test. + * + * @details This test verifies the interrupt functionality of the SPI interface. + * The test verifies that an SPI transfer can be triggered from the SPI + * interrupt service routine context. + * + * The test basically repeats the #SpiConnectionTest but this time it + * invokes the second SPI transfer from the SPI callback function. + * A very common error is that the callback is invoked while the SPI + * module is still busy which does not allow to invoke another SPI + * transfer from the callback. + * + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if the test did not finish within a specified time. + * - #ERROR_FAIL if the device access failed and the read data did not + * match the expected values. + * - The S2PI layer error code if #S2PI_TransferFrame or the SPI + * callback yield in any non-OK status. + *****************************************************************************/ +static status_t SpiTransferFromSpiInterrupt(s2pi_slave_t slave) +{ + /* Test parameter configuration: *****************************************/ + const uint32_t timeout_us = 100000; // timeout for SPI transfers to finish + /*************************************************************************/ + + status_t status = STATUS_OK; + spi_irq_data_t data = { .Slave = slave }; + + print("Invoking SPI transfer from task level...\n"); + + /* Transfer a pattern to the register */ + data.Data[0] = 0x04; // Laser Pattern Register Address + + for (uint8_t i = 1; i < 17U; ++i) { data.Data[i] = i; } + + status = S2PI_TransferFrame(slave, data.Data, data.Data, 17U, + SpiTransferFromSpiInterruptCallback, &data); + + if (status != STATUS_OK) { + error_log("SPI transfer from SPI interrupt test failed:\n" + "Failed to transfer a data frame! Error code: %d", status); + return status; + } + + /* Wait until transfers has finished. */ + ltc_t start; + Time_GetNow(&start); + + while (!data.Finished && (data.Status == STATUS_OK)) { + if (Time_CheckTimeoutUSec(&start, timeout_us)) { + const uint32_t elapsed_us = Time_GetElapsedUSec(&start); + error_log("SPI transfer from SPI interrupt test failed:\n" + "Waiting for the transfers to be finished yielded a timeout.\n" + "Timeout: %d us; Elapsed: %d us (%d of %d events).", + timeout_us, elapsed_us); + status = ERROR_TIMEOUT; + break; + } + } + + if (data.Status != STATUS_OK) { + error_log("SPI transfer from SPI interrupt test failed:\n" + "Waiting for the transfers to be finished yielded a error code: %d", + data.Status); + return data.Status; + } + + print("Verify read data...\n"); + + /* Verify the read pattern. */ + for (uint8_t i = 1; i < 17U; ++i) { + if (data.Data[i] != i) { + error_log("SPI transfer from SPI interrupt test failed:\n" + "Verification of read data is invalid!\n" + "read_data[%d] = %d, but expected was %d", + i, data.Data[i], i); + return ERROR_FAIL; + } + } + + return STATUS_OK; +} + +/*!*************************************************************************** + * @brief GPIO interrupt callback function for the SPI transfer from IRQ test. + * + * @details The interrupt callback is invoked from the S2PI module upon + * receiving an GPIO interrupt from the devices IRQ pin. The callback + * is used by the #SpiTransferFromGpioInterrupt test to trigger the + * first SPI transfer from the interrupt callback context. + * + * @note The callback also utilizes the #print functionality. This requires + * a correct implementation of the corresponding function such that it + * can be invoked from the given interrupt context. This usually + * requires the underlying send (e.g. UART or USB send functions) to + * have higher priority that this interrupt in order to finished the + * print statement asynchronously. + * + * @param param The abstract interrupt callback parameter. + *****************************************************************************/ +static void SpiTransferFromGpioInterruptCallback(void *param) +{ + if (param == NULL) { + error_log("SPI transfer from GPIO interrupt test failed:\n" + "callback parameter \"param\" was NULL!"); + return; + } + + print("Invoking SPI transfer from GPIO interrupt callback...\n"); + + /* Clear the laser pattern and read back previous values. */ + spi_irq_data_t *data = (spi_irq_data_t *) param; + data->Data[0] = 0x04; // Laser Pattern Register Address + + for (uint8_t i = 1; i < 17U; ++i) { data->Data[i] = i; } + + status_t status = S2PI_TransferFrame(data->Slave, data->Data, data->Data, 17U, + SpiTransferFromSpiInterruptCallback, param); + + if (status != STATUS_OK) { + error_log("SPI transfer from GPIO interrupt test failed:\n" + "Calling S2PI_TransferFrame from GPIO interrupt " + "returned error code: %d", status); + data->Status = status; + return; + } +} + +/*!*************************************************************************** + * @brief SPI transfer from GPIO interrupt callback test. + * + * @details This test verifies the interrupt functionality of the SPI interface. + * The test verifies that an SPI transfer can be triggered from the + * GPIO interrupt service routine context. + * + * The test basically repeats the #SpiTransferFromSpiInterrupt but + * this time it invokes the first SPI transfer from the GPIO callback + * function. In order to trigger a GPIO interrupt, the device is + * configured and a measurement is started (see #GpioInterruptTest). + * + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if the test did not finish within a specified time. + * - #ERROR_FAIL if the device access failed and the read data did not + * match the expected values. + * - The S2PI layer error code if #S2PI_TransferFrame or the GPIO or + * SPI callback yield in any non-OK status. + *****************************************************************************/ +static status_t SpiTransferFromGpioInterrupt(s2pi_slave_t slave) +{ + /* Test parameter configuration: *****************************************/ + const uint32_t timeout_ms = 300; // timeout for measurement, might be increased.. + /*************************************************************************/ + + spi_irq_data_t data = { .Slave = slave }; + + /* Install IRQ callback. */ + status_t status = S2PI_SetIrqCallback(slave, SpiTransferFromGpioInterruptCallback, &data); + + if (status != STATUS_OK) { + error_log("SPI transfer from GPIO interrupt test failed:\n" + "The call to S2PI_SetIrqCallback returned error code: %d", status); + return status; + } + + /* Setup Device for invoking GPIO interrupt. */ + status = ConfigureDevice(slave, 0); + + if (status != STATUS_OK) { + error_log("SPI transfer from GPIO interrupt test failed."); + return status; + } + + /* Trigger Measurement and invoke GPIO interrupt. */ + status = TriggerMeasurement(slave, 0, 0, 0); + + if (status != STATUS_OK) { + error_log("GPIO interrupt test failed!"); + return status; + } + + ltc_t start; + Time_GetNow(&start); + + /* Wait for Interrupt using the callback method. */ + while (!data.Finished) { + if (Time_CheckTimeoutMSec(&start, timeout_ms)) { + error_log("SPI transfer from GPIO interrupt test failed:\n" + "The IRQ callback was not invoked within %d ms.", + timeout_ms); + return ERROR_TIMEOUT; + } + } + + if (data.Status != STATUS_OK) { + error_log("SPI transfer from GPIO interrupt test failed:\n" + "Waiting for the transfers to be finished yielded a error code: %d", + data.Status); + return data.Status; + } + + print("Verify read data...\n"); + + /* Verify the read pattern. */ + for (uint8_t i = 1; i < 17U; ++i) { + if (data.Data[i] != i) { + error_log("SPI transfer from GPIO interrupt test failed:\n" + "Verification of read data is invalid!\n" + "read_data[%d] = %d, but expected was %d", + i, data.Data[i], i); + return ERROR_FAIL; + } + } + + return STATUS_OK; +} + +/*!*************************************************************************** + * @brief PIT interrupt callback function for the SPI transfer from IRQ test. + * + * @details The interrupt callback is invoked from the PIT module upon periodic + * timeout event. The callback is used by the + * #SpiTransferFromPitInterrupt test to trigger the first SPI transfer + * from the interrupt callback context. + * + * @note The callback also utilizes the #print functionality. This requires + * a correct implementation of the corresponding function such that it + * can be invoked from the given interrupt context. This usually + * requires the underlying send (e.g. UART or USB send functions) to + * have higher priority that this interrupt in order to finished the + * print statement asynchronously. + * + * @param param The abstract interrupt callback parameter. + *****************************************************************************/ +static void SpiTransferFromPitInterruptCallback(void *param) +{ + status_t status = Timer_SetInterval(0, param); // disable timer + + if (status != STATUS_OK) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "Timer_SetCallback to 0 returned status code: %d", + status); + + if (param != NULL) { ((spi_irq_data_t *)param)->Status = status; } + + return; + } + + + if (param == NULL) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "callback parameter \"param\" was NULL!"); + return; + } + + print("Invoking SPI transfer from PIT interrupt callback...\n"); + + /* Clear the laser pattern and read back previous values. */ + spi_irq_data_t *data = (spi_irq_data_t *) param; + data->Data[0] = 0x04; // Laser Pattern Register Address + + for (uint8_t i = 1; i < 17U; ++i) { data->Data[i] = i; } + + status = S2PI_TransferFrame(data->Slave, data->Data, data->Data, 17U, + SpiTransferFromSpiInterruptCallback, param); + + if (status != STATUS_OK) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "Calling S2PI_TransferFrame from GPIO interrupt " + "returned error code: %d", status); + data->Status = status; + return; + } +} + +/*!*************************************************************************** + * @brief SPI transfer from PIT interrupt callback test. + * + * @details This test verifies the interrupt functionality of the SPI interface. + * The test verifies that an SPI transfer can be triggered from the + * PIT interrupt service routine context. + * + * The test basically repeats the #SpiTransferFromSpiInterrupt but + * this time it invokes the first SPI transfer from the PIT callback + * function. In order to trigger a PIT interrupt, the timer is + * configured with a small interval and immediately disabled upon the + * first event. + * + * Note that this test is only executed if the PIT module is actually + * implemented. + * + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_NOT_IMPLEMENTED if the PIT functionality is not + * implemented and the test is skipped. + * - #ERROR_TIMEOUT if the test did not finish within a specified time. + * - #ERROR_FAIL if the device access failed and the read data did not + * match the expected values. + * - The S2PI layer error code if #S2PI_TransferFrame or the SPI + * callback yield in any non-OK status. + * - The PIT layer error code if #Timer_SetCallback or the PIT + * callback yield in any non-OK status. + *****************************************************************************/ +static status_t SpiTransferFromPitInterrupt(s2pi_slave_t slave) +{ + /* Test parameter configuration: *****************************************/ + const uint32_t timeout_ms = 100; // timeout for test. + const uint32_t interval_us = 1000; // PIT interval for the first event. + /*************************************************************************/ + + spi_irq_data_t data = { .Slave = slave }; + + status_t status = Timer_SetCallback(SpiTransferFromPitInterruptCallback); + + if (status == ERROR_NOT_IMPLEMENTED) { return status; } + + if (status != STATUS_OK) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "Timer_SetCallback returned status code: %d", status); + return status; + } + + /* Setup the PIT callback with specified interval. */ + status = Timer_SetInterval(interval_us, &data); + + if (status != STATUS_OK) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "Timer_SetInterval returned status code: %d", status); + return status; + } + + ltc_t start; + Time_GetNow(&start); + + /* Wait for test to be finished. */ + while (!data.Finished) { + if (Time_CheckTimeoutMSec(&start, timeout_ms)) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "The IRQ callback was not invoked within %d ms.", + timeout_ms); + return ERROR_TIMEOUT; + } + } + + if (data.Status != STATUS_OK) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "Waiting for the transfers to be finished yielded a error code: %d", + data.Status); + return data.Status; + } + + status = Timer_SetCallback(0); + + if (status != STATUS_OK) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "Timer_SetCallback to 0 returned status code: %d", status); + return status; + } + + print("Verify read data...\n"); + + /* Verify the read pattern. */ + for (uint8_t i = 1; i < 17U; ++i) { + if (data.Data[i] != i) { + error_log("SPI transfer from PIT interrupt test failed:\n" + "Verification of read data is invalid!\n" + "read_data[%d] = %d, but expected was %d", + i, data.Data[i], i); + return ERROR_FAIL; + } + } + + return STATUS_OK; +} + +/*!*************************************************************************** + * @brief SPI Transfer from Interrupt Test for S2PI HAL Implementation. + * + * @details This test verifies the interrupt functionality of the SPI interface. + * The test verifies that an SPI transfer can be triggered from the + * interrupt service routine context. I.e. the #S2PI_TransferFrame + * function is called from the following interrupts: + * - SPI interrupt + * - GPIO interrupt + * - PIT interrupt (optional, if PIT is implemented) + * + * @warning The test utilizes already the timer HAL in order to implement a + * rudimentary timeout. However, at this time, only some basic + * plausibility checks are performed on the timer HAL. I.e. if there + * is an issue in the time HAL, e.g. too fast or too slow time + * counting, the test may fail with an #ERROR_TIMEOUT. In this case, + * one also needs to verify the timer HAL, especially the + * #Timer_GetCounterValue function. + * + * @param slave The S2PI slave parameter passed to the S2PI HAL functions. + * + * @return Returns the \link #status_t status\endlink: + * - #STATUS_OK on success. + * - #ERROR_TIMEOUT if the operation did not finished within a specified + * time (check also timer HAL implementation). + * - #ERROR_FAIL if the device access failed and the read data did not + * match the expected values. + * - The S2PI layer error code if #S2PI_TransferFrame or #S2PI_GetStatus + * return any negative status. + *****************************************************************************/ +static status_t SpiTransferFromInterruptTest(s2pi_slave_t slave) +{ + status_t status = STATUS_OK; + + print(" .1 >> SPI Transfer from SPI Interrupt Test\n"); + status = SpiTransferFromSpiInterrupt(slave); + + if (status != STATUS_OK) { return status; } + + print(" .1 >> PASS\n\n"); + + print(" .2 >> SPI Transfer from GPIO Interrupt Test\n"); + status = SpiTransferFromGpioInterrupt(slave); + + if (status != STATUS_OK) { return status; } + + print(" .2 >> PASS\n\n"); + + print(" .3 >> SPI Transfer from PIT Interrupt Test\n"); + status = SpiTransferFromPitInterrupt(slave); + + if (status == ERROR_NOT_IMPLEMENTED) { + print(" .3 >> SKIPPED (PIT is not implemented)\n\n"); + + } else { + if (status != STATUS_OK) { return status; } + + print(" .3 >> PASS\n\n"); + } + + return STATUS_OK; +} + +/*! @} */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.h b/src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.h index 5af6660dfa..be9c0071e4 100644 --- a/src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.h +++ b/src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.h @@ -1,10 +1,10 @@ /*************************************************************************//** - * @file argus_hal_test.c - * @brief Tests for the AFBR-S50 API hardware abstraction layer. + * @file + * @brief Tests for the AFBR-S50 API hardware abstraction layer. * * @copyright * - * Copyright (c) 2021, Broadcom, Inc. + * Copyright (c) 2023, Broadcom Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,147 +41,192 @@ extern "C" { #endif - /*!*************************************************************************** - * @defgroup argustest HAL Self Test + * @defgroup argus_test HAL Self Test + * @ingroup argus * - * @brief A test module to verify implementation of the HAL. + * @brief A test module to verify implementation of the HAL. * - * @details A series of automated tests that can be executed on the target - * platform in order to verify the implementation of the HAL that - * are required by the API. + * @details A series of automated tests that can be executed on the target + * platform in order to verify the implementation of the HAL that + * are required by the API. * - * @addtogroup argustest + * See #Argus_VerifyHALImplementation for a detailed documentation. + * + * @addtogroup argus_test * @{ *****************************************************************************/ #include "argus.h" /*!*************************************************************************** - * @brief Version number of the HAL Self Test. + * @brief Version number of the HAL Self Test. * * @details Changes: - * * v1.0: - * - Initial release. - * * v1.1: - * - Added additional print output. - * - Increased tolerance for timer test to 3%. - * - Fixed callback issue by disabling it after IRQ test. - * * v1.1: - * - Added PIT test cases. + * * v1.0: + * - Initial release. + * * v1.1: + * - Added additional print output. + * - Increased tolerance for timer test to 3%. + * - Fixed callback issue by disabling it after IRQ test. + * * v1.2: + * - Added PIT test cases. + * * v1.3: + * - Added test case for SPI maximum data transfer size. + * - Added tests for SPI transfers invoked from all IRQ callbacks. + * - Added verification of first PIT event occurrence. + * - Relaxed PIT pass conditions (0.1% -> 0.5%) + * * v1.4: + * - Adopted to new multi-device HAL interface of API v1.4.4 release. + * - Added verification of SPI callback invocation. + * - Updated GPIO interrupt test to verify if delayed interrupt + * pending states can be detected via #S2PI_ReadIrqPin. + * *****************************************************************************/ -#define HAL_TEST_VERSION "v1.2" +#define HAL_TEST_VERSION "v1.4" /*!*************************************************************************** - * @brief Executes a series of tests in order to verify the HAL implementation. + * @brief Executes a series of tests in order to verify the HAL implementation. * * @details A series of automated tests are executed on the target platform in - * order to verify the implementation of the HAL that are required by - * the API. + * order to verify the implementation of the HAL that are required by + * the API. * - * Each test will write an error description via the print (i.e. UART) - * function that shows what went wrong. Also an corresponding status is - * returned in case no print functionality is available. + * Each test will write an error description via the print (i.e. UART) + * function that shows what went wrong. Also an corresponding status is + * returned in case no print functionality is available. * - * The following tests are executed: + * The following tests are executed: * - * **1) Timer Plausibility Test:** + * **1) Timer Plausibility Test:** * - * Rudimentary tests of the lifetime counter (LTC) implementation. - * This verifies that the LTC is running by checking if the returned - * values of two consecutive calls to the #Timer_GetCounterValue - * function are ascending. An artificial delay using the NOP operation - * is induced such that the timer is not read to fast. + * Rudimentary tests of the lifetime counter (LTC) implementation. + * This verifies that the LTC is running by checking if the returned + * values of two consecutive calls to the #Timer_GetCounterValue + * function are ascending. An artificial delay using the NOP operation + * is induced such that the timer is not read to fast. * - * **2) Timer Wraparound Test:** + * **2) Timer Wraparound Test:** * - * The LTC values must wrap from 999999 µs to 0 µs and increase the - * seconds counter accordingly. This test verifies the correct wrapping - * by consecutively calling the #Timer_GetCounterValue function until - * at least 2 wraparound events have been occurred. + * The LTC values must wrap from 999999 µs to 0 µs and increase the + * seconds counter accordingly. This test verifies the correct wrapping + * by consecutively calling the #Timer_GetCounterValue function until + * at least 2 wraparound events have been occurred. * - * **3) SPI Connection Test:** + * **3) SPI Connection Test:** * - * This test verifies the basic functionality of the SPI interface. - * The test utilizes the devices laser pattern register, which can - * be freely programmed by any 128-bit pattern. Thus, it writes a byte - * sequence and reads back the written values on the consecutive SPI - * access. + * This test verifies the basic functionality of the SPI interface. + * The test utilizes the devices laser pattern register, which can + * be freely programmed by any 128-bit pattern. Thus, it writes a byte + * sequence and reads back the written values on the consecutive SPI + * access. * - * **4) SPI Interrupt Test:** + * **4) SPI Maximum Data Length Test**: * - * This test verifies the correct implementation of the device - * integration finished interrupt callback. Therefore it configures - * the device with a minimal setup to run a pseudo measurement that - * does not emit any laser light. + * This test verifies the maximum data transfer length of the SPI + * interface. The test sends and receives up to 396 data bytes plus + * a single address byte over the SPI interface and verifies that no + * data get lost. * - * Note that this test does verify the GPIO interrupt that occurs - * whenever the device has finished the integration/measurement and - * new data is waiting to be read from the device. This does not test - * the interrupt that is triggered when the SPI transfer has finished. + * **5) SPI Interrupt Test:** * - * The data ready interrupt implies two S2PI layer functions that - * are tested in this test: The #S2PI_SetIrqCallback function installs - * a callback function that is invoked whenever the IRQ occurs. - * The IRQ can be delayed due to higher priority task, e.g. from the - * user code. It is essential for the laser safety timeout algorithm - * to determine the device ready signal as fast as possible, another - * method is implemented to read if the IRQ is pending but the - * callback has not been reset yet. This is what the #S2PI_ReadIrqPin - * function is for. + * This test verifies the correct implementation of the device + * integration finished interrupt callback. Therefore it configures + * the device with a minimal setup to run a pseudo measurement that + * does not emit any laser light. * - * **5) GPIO Mode Test:** + * Note that this test does verify the GPIO interrupt that occurs + * whenever the device has finished the integration/measurement and + * new data is waiting to be read from the device. This does not test + * the interrupt that is triggered when the SPI transfer has finished. * - * This test verifies the GPIO mode of the S2PI HAL module. This is - * done by leveraging the EEPROM readout sequence that accesses the - * devices EEPROM via a software protocol that depends on the GPIO - * mode. + * The data ready interrupt implies two S2PI layer functions that + * are tested in this test: The #S2PI_SetIrqCallback function installs + * a callback function that is invoked whenever the IRQ occurs. + * The IRQ can be delayed due to higher priority task, e.g. from the + * user code. It is essential for the laser safety timeout algorithm + * to determine the device ready signal as fast as possible, another + * method is implemented to read if the IRQ is pending but the + * callback has not been reset yet. This is what the #S2PI_ReadIrqPin + * function is for. * - * This the requires several steps, most of them are already verified - * in previous tests: + * **6) GPIO Mode Test:** * - * - Basic device configuration and enable EEPROM. - * - Read EERPOM via GPIO mode and apply Hamming weight. - * - Repeat several times (to eliminate random readout issues). - * - Decode the EEPROM (using EEPROM_Decode in argus_cal_eeprom.c). - * - Check if Module Number and Chip ID is not 0. + * This test verifies the GPIO mode of the S2PI HAL module. This is + * done by leveraging the EEPROM readout sequence that accesses the + * devices EEPROM via a software protocol that depends on the GPIO + * mode. * - * **6) Timer Test for Lifetime Counter:** + * This the requires several steps, most of them are already verified + * in previous tests: * - * The test verifies the lifetime counter timer HAL implementation by - * comparing the timings to the AFBR-S50 device as a reference. - * Therefore several measurement are executed on the device, each with - * a different averaging sample count. The elapsed time increases - * linearly with the number of averaging samples. In order to remove - * the time for software/setup, a linear regression fit is applied to - * the measurement results and only the slope is considered for the - * result. A delta of 102.4 microseconds per sample is expected. - * If the measured delta per sample is within an specified error range, - * the timer implementation is considered correct. + * - Basic device configuration and enable EEPROM. + * - Read EERPOM via GPIO mode and apply Hamming weight. + * - Repeat several times (to eliminate random readout issues). + * - Decode the EEPROM (using EEPROM_Decode in argus_cal_eeprom.c). + * - Check if Module Number and Chip ID is not 0. * - * **7) Timer Test for Periodic Interrupt Timer:** + * **7) Timer Test for Lifetime Counter:** * - * The test verifies the correct implementation of the periodic - * interrupt timer (PIT). It sets different intervals and waits for - * a certain number of interrupts to happen. Each interrupt event - * is counted and the time between the first and the last interrupt - * is measured. Finally, the measured interval is compared to the - * expectations. + * The test verifies the lifetime counter timer HAL implementation by + * comparing the timings to the AFBR-S50 device as a reference. + * Therefore several measurement are executed on the device, each with + * a different averaging sample count. The elapsed time increases + * linearly with the number of averaging samples. In order to remove + * the time for software/setup, a linear regression fit is applied to + * the measurement results and only the slope is considered for the + * result. A delta of 102.4 microseconds per sample is expected. + * If the measured delta per sample is within an specified error range, + * the timer implementation is considered correct. * + * **8) Timer Test for Periodic Interrupt Timer (optional):** * - * @param spi_slave The SPI hardware slave, i.e. the specified CS and IRQ - * lines. This is actually just a number that is passed - * to the SPI interface to distinct for multiple SPI slave - * devices. Note that the slave must be not equal to 0, - * since is reserved for error handling. + * The test verifies the correct implementation of the periodic + * interrupt timer (PIT). It sets different intervals and waits for + * a certain number of interrupts to happen. Each interrupt event + * is counted and the time between the first and the last interrupt + * is measured. Finally, the measured interval is compared to the + * expectations. * - * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * Note that this test is only executed if the PIT is actually + * implemented. Otherwise, the test is skipped. + * + * **9) SPI Transfer from Interrupt Callback Test:** + * + * The test verifies that the #S2PI_TransferFrame method of the + * S2PI layer can be invoked from a interrupt callback function too. + * Thus, it repeats the S2PI Connection Test but this time from + * different interrupt callback functions: + * + * - SPI Callback: The first transfer is invoked from thread level, + * the second transfer is invoke from the SPI interrupt callback + * function. + * + * - GPIO Callback: The device is setup to trigger an GPIO interrupt + * (see also the SPI Interrupt Test). The corresponding GPIO + * interrupt callback function will trigger the first transfer while + * the second one is triggered from the SPI callback function. + * + * - PIT Callback (optional): This test is only executed optional if + * the PIT interface is implemented. The test sequence is the same + * as for the GPIO callback, but the first transfer is triggered + * from the PIT callback function. + * + * @note See #HAL_TEST_VERSION for a version history and change log of + * the HAL self tests. + * + * @param spi_slave The SPI hardware slave, i.e. the specified CS and IRQ + * lines. This is actually just a number that is passed + * to the SPI interface to distinct for multiple SPI slave + * devices. Note that the slave must be not equal to 0, + * since is reserved for error handling. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). *****************************************************************************/ status_t Argus_VerifyHALImplementation(s2pi_slave_t spi_slave); -#ifdef __cplusplus -} -#endif - /*! @} */ -#endif /* ARGUS_CAL_API_H */ +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* ARGUS_HAL_TEST_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/parameters.c b/src/drivers/distance_sensor/broadcom/afbrs50/parameters.c new file mode 100644 index 0000000000..28ef2a17c1 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/parameters.c @@ -0,0 +1,102 @@ +/**************************************************************************** + * + * Copyright (c) 2023 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. + * + ****************************************************************************/ + +/** + * AFBR Rangefinder Mode + * + * This parameter defines the mode of the AFBR Rangefinder. + * + * @reboot_required true + * @min 0 + * @max 3 + * @group Sensors + * + * @value 0 Short Range Mode + * @value 1 Long Range Mode + * @value 2 High Speed Short Range Mode + * @value 3 High Speed Long Range Mode + */ +PARAM_DEFINE_INT32(SENS_AFBR_MODE, 1); + +/** + * AFBR Rangefinder Short Range Rate + * + * This parameter defines measurement rate of the AFBR Rangefinder in short range mode. + * + * @min 1 + * @max 100 + * @group Sensors + * + */ +PARAM_DEFINE_INT32(SENS_AFBR_S_RATE, 50); + +/** + * AFBR Rangefinder Long Range Rate + * + * This parameter defines measurement rate of the AFBR Rangefinder in long range mode. + * + * @min 1 + * @max 100 + * @group Sensors + * + */ +PARAM_DEFINE_INT32(SENS_AFBR_L_RATE, 25); + +/** + * AFBR Rangefinder Short/Long Range Threshold + * + * This parameter defines the threshold for switching between short and long range mode. + * The mode will switch from short to long range when the distance is greater than the threshold plus the hysteresis. + * The mode will switch from long to short range when the distance is less than the threshold minus the hysteresis. + * + * @unit m + * @min 1 + * @max 50 + * @group Sensors + * + */ +PARAM_DEFINE_INT32(SENS_AFBR_THRESH, 5); + + +/** + * AFBR Rangefinder Short/Long Range Threshold Hysteresis + * + * This parameter defines the hysteresis for switching between short and long range mode. + * + * @unit m + * @min 1 + * @max 10 + * @group Sensors + * + */ +PARAM_DEFINE_INT32(SENS_AFBR_HYSTER, 1); diff --git a/src/drivers/uavcan/sensors/rangefinder.cpp b/src/drivers/uavcan/sensors/rangefinder.cpp index cff084292c..5e15b8d51f 100644 --- a/src/drivers/uavcan/sensors/rangefinder.cpp +++ b/src/drivers/uavcan/sensors/rangefinder.cpp @@ -108,14 +108,13 @@ void UavcanRangefinderBridge::range_sub_cb(const _inited = true; } - /* - * FIXME HACK - * This code used to rely on msg.getMonotonicTimestamp().toUSec() instead of HRT. - * It stopped working when the time sync feature has been introduced, because it caused libuavcan - * to use an independent time source (based on hardware TIM5) instead of HRT. - * The proper solution is to be developed. - */ - rangefinder->update(hrt_absolute_time(), msg.range); + int8_t quality = -1; + + if (msg.reading_type == uavcan::equipment::range_sensor::Measurement::READING_TYPE_VALID_RANGE) { + quality = 100; + } + + rangefinder->update(hrt_absolute_time(), msg.range, quality); } int UavcanRangefinderBridge::init_driver(uavcan_bridge::Channel *channel)