From dddd2c3297bd57a92d3b0261e2702f4f36d8a774 Mon Sep 17 00:00:00 2001 From: AlexKlimaj Date: Tue, 15 Jun 2021 10:12:24 -0600 Subject: [PATCH] drivers/distance_sensor: New Broadcom AFBR-S50LV85D distance sensor driver * Basic Broadcom AFBR-S50 driver using vendor API and binary blob https://github.com/Broadcom/AFBR-S50-API * fix ARK Flow paw3902 rotation --- .gitignore | 2 + boards/ark/can-flow/debug.cmake | 6 +- boards/ark/can-flow/default.cmake | 4 +- boards/ark/can-flow/init/rc.board_sensors | 4 +- .../ark/can-flow/nuttx-config/include/board.h | 6 +- .../nuttx-config/include/board_dma_map.h | 2 - .../ark/can-flow/nuttx-config/nsh/defconfig | 2 - boards/ark/can-flow/src/board_config.h | 7 + .../imxrt/board_hw_info/board_hw_rev_ver.c | 4 +- .../px4/nxp/k66/include/px4_arch/micro_hal.h | 3 +- .../nxp/rt106x/include/px4_arch/micro_hal.h | 3 +- .../board_hw_info/board_hw_rev_ver.c | 4 +- .../stm32_common/include/px4_arch/micro_hal.h | 3 +- src/drivers/distance_sensor/CMakeLists.txt | 1 + .../distance_sensor/broadcom/CMakeLists.txt | 34 + .../broadcom/afbrs50/AFBRS50.cpp | 360 +++++ .../broadcom/afbrs50/AFBRS50.hpp | 92 ++ .../broadcom/afbrs50/API/Inc/irq.h | 35 + .../broadcom/afbrs50/API/Inc/s2pi.h | 107 ++ .../broadcom/afbrs50/API/Inc/timer.h | 52 + .../broadcom/afbrs50/API/Src/irq.c | 24 + .../broadcom/afbrs50/API/Src/s2pi.c | 431 ++++++ .../broadcom/afbrs50/API/Src/timer.c | 138 ++ .../broadcom/afbrs50/CMakeLists.txt | 59 + .../broadcom/afbrs50/Inc/api/argus_api.h | 1185 +++++++++++++++++ .../broadcom/afbrs50/Inc/api/argus_dca.h | 489 +++++++ .../broadcom/afbrs50/Inc/api/argus_def.h | 205 +++ .../broadcom/afbrs50/Inc/api/argus_dfm.h | 81 ++ .../broadcom/afbrs50/Inc/api/argus_meas.h | 118 ++ .../broadcom/afbrs50/Inc/api/argus_msk.h | 170 +++ .../broadcom/afbrs50/Inc/api/argus_pba.h | 221 +++ .../broadcom/afbrs50/Inc/api/argus_px.h | 143 ++ .../broadcom/afbrs50/Inc/api/argus_res.h | 173 +++ .../broadcom/afbrs50/Inc/api/argus_snm.h | 82 ++ .../broadcom/afbrs50/Inc/api/argus_status.h | 271 ++++ .../broadcom/afbrs50/Inc/api/argus_version.h | 76 ++ .../broadcom/afbrs50/Inc/api/argus_xtalk.h | 114 ++ .../broadcom/afbrs50/Inc/argus.h | 50 + .../broadcom/afbrs50/Inc/platform/argus_irq.h | 121 ++ .../broadcom/afbrs50/Inc/platform/argus_nvm.h | 135 ++ .../afbrs50/Inc/platform/argus_print.h | 83 ++ .../afbrs50/Inc/platform/argus_s2pi.h | 352 +++++ .../afbrs50/Inc/platform/argus_timer.h | 267 ++++ .../broadcom/afbrs50/Inc/utility/fp_def.h | 407 ++++++ .../broadcom/afbrs50/Inc/utility/time.h | 290 ++++ .../broadcom/afbrs50/Lib/libafbrs50_m4_fpu.a | Bin 0 -> 226886 bytes .../afbrs50/Lib/libafbrs50_m4_fpu_os.a | Bin 0 -> 186050 bytes .../broadcom/afbrs50/argus_hal_test.c | 1174 ++++++++++++++++ .../broadcom/afbrs50/argus_hal_test.h | 166 +++ src/drivers/drv_sensor.h | 32 +- .../drivers/rangefinder/PX4Rangefinder.hpp | 2 +- 51 files changed, 7753 insertions(+), 37 deletions(-) create mode 100644 src/drivers/distance_sensor/broadcom/CMakeLists.txt create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.cpp create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.hpp create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/irq.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/s2pi.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/timer.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/API/Src/irq.c create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/API/Src/s2pi.c create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/API/Src/timer.c create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/CMakeLists.txt create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_api.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dca.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_def.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dfm.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_meas.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_msk.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_pba.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_px.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_res.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_snm.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_status.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_version.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_xtalk.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/argus.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_irq.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_nvm.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_print.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_s2pi.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_timer.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_def.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/time.h create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Lib/libafbrs50_m4_fpu.a create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/Lib/libafbrs50_m4_fpu_os.a create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.c create mode 100644 src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.h diff --git a/.gitignore b/.gitignore index 2e467da624..5d37a8e4e2 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,5 @@ src/systemcmds/topic_listener/listener_generated.cpp # SITL dataman eeprom/ + +!src/drivers/distance_sensor/broadcom/afbrs50/Lib/* diff --git a/boards/ark/can-flow/debug.cmake b/boards/ark/can-flow/debug.cmake index ddcb65e867..3a80b7f028 100644 --- a/boards/ark/can-flow/debug.cmake +++ b/boards/ark/can-flow/debug.cmake @@ -12,6 +12,7 @@ px4_add_board( UAVCAN_INTERFACES 1 DRIVERS bootloaders + distance_sensor/broadcom/afbrs50 imu/bosch/bmi088 optical_flow/paw3902 uavcannode @@ -20,14 +21,13 @@ px4_add_board( load_mon #sensors SYSTEMCMDS - mft - mtd param perf reboot system_time top - topic_listener + #topic_listener + uorb ver work_queue ) diff --git a/boards/ark/can-flow/default.cmake b/boards/ark/can-flow/default.cmake index 7ac68b3f10..4b966eb29a 100644 --- a/boards/ark/can-flow/default.cmake +++ b/boards/ark/can-flow/default.cmake @@ -13,6 +13,7 @@ px4_add_board( UAVCAN_INTERFACES 1 DRIVERS bootloaders + distance_sensor/broadcom/afbrs50 imu/bosch/bmi088 optical_flow/paw3902 uavcannode @@ -21,14 +22,13 @@ px4_add_board( #load_mon #sensors SYSTEMCMDS - mft - mtd param #perf #reboot #system_time #top #topic_listener + #uorb #ver #work_queue ) diff --git a/boards/ark/can-flow/init/rc.board_sensors b/boards/ark/can-flow/init/rc.board_sensors index 7750a31898..696b813919 100644 --- a/boards/ark/can-flow/init/rc.board_sensors +++ b/boards/ark/can-flow/init/rc.board_sensors @@ -4,6 +4,8 @@ #------------------------------------------------------------------------------ # Internal SPI -paw3902 -s start -Y 90 +paw3902 -s start -Y 180 bmi088 -A -s -R 4 start bmi088 -G -s -R 4 start + +afbrs50 start diff --git a/boards/ark/can-flow/nuttx-config/include/board.h b/boards/ark/can-flow/nuttx-config/include/board.h index cd6ea967db..58876a44ce 100644 --- a/boards/ark/can-flow/nuttx-config/include/board.h +++ b/boards/ark/can-flow/nuttx-config/include/board.h @@ -133,8 +133,8 @@ #define GPIO_SPI1_MOSI GPIO_SPI1_MOSI_1 #define GPIO_SPI1_SCK GPIO_SPI1_SCK_1 -#define GPIO_SPI2_MISO GPIO_SPI2_MISO_1 -#define GPIO_SPI2_MOSI GPIO_SPI2_MOSI_1 -#define GPIO_SPI2_SCK GPIO_SPI2_SCK_1 +#define GPIO_SPI2_MISO GPIO_SPI2_MISO_1 /* PB14 */ +#define GPIO_SPI2_MOSI GPIO_SPI2_MOSI_1 /* PB15 */ +#define GPIO_SPI2_SCK GPIO_SPI2_SCK_1 /* PB10 */ #endif /* __ARCH_BOARD_BOARD_H */ diff --git a/boards/ark/can-flow/nuttx-config/include/board_dma_map.h b/boards/ark/can-flow/nuttx-config/include/board_dma_map.h index 2d52cfbfec..f6e577b3d5 100644 --- a/boards/ark/can-flow/nuttx-config/include/board_dma_map.h +++ b/boards/ark/can-flow/nuttx-config/include/board_dma_map.h @@ -40,6 +40,4 @@ // DMA2 Channel/Stream Selections //--------------------------------------------//---------------------------//---------------- #define DMACHAN_SPI1_RX DMAMAP_SPI1_RX_2 // DMA2, Stream 2, Channel 3 -#define DMACHAN_SPI2_RX DMAMAP_SPI2_RX // DMA2, Stream 3, Channel 0 -#define DMACHAN_SPI2_TX DMAMAP_SPI2_TX // DMA2, Stream 4, Channel 0 #define DMACHAN_SPI1_TX DMAMAP_SPI1_TX_1 // DMA2, Stream 5, Channel 3 diff --git a/boards/ark/can-flow/nuttx-config/nsh/defconfig b/boards/ark/can-flow/nuttx-config/nsh/defconfig index de632fc595..006729f025 100644 --- a/boards/ark/can-flow/nuttx-config/nsh/defconfig +++ b/boards/ark/can-flow/nuttx-config/nsh/defconfig @@ -144,8 +144,6 @@ CONFIG_STM32_SPI1=y CONFIG_STM32_SPI1_DMA=y CONFIG_STM32_SPI1_DMA_BUFFER=1024 CONFIG_STM32_SPI2=y -CONFIG_STM32_SPI2_DMA=y -CONFIG_STM32_SPI2_DMA_BUFFER=1024 CONFIG_STM32_SPI_DMA=y CONFIG_STM32_TIM8=y CONFIG_STM32_USART2=y diff --git a/boards/ark/can-flow/src/board_config.h b/boards/ark/can-flow/src/board_config.h index 44424aeae7..8096f5a5ad 100644 --- a/boards/ark/can-flow/src/board_config.h +++ b/boards/ark/can-flow/src/board_config.h @@ -56,6 +56,13 @@ #define GPIO_nLED_RED /* PB3 */ (GPIO_OUTPUT|GPIO_OPENDRAIN|GPIO_SPEED_50MHz|GPIO_OUTPUT_SET|GPIO_PORTB|GPIO_PIN3) #define GPIO_nLED_BLUE /* PA8 */ (GPIO_OUTPUT|GPIO_OPENDRAIN|GPIO_SPEED_50MHz|GPIO_OUTPUT_SET|GPIO_PORTA|GPIO_PIN8) +#define BROADCOM_AFBR_S50_S2PI_SPI_BUS 2 +#define BROADCOM_AFBR_S50_S2PI_CS /* PB12 */ (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|GPIO_OUTPUT_SET|GPIO_PORTB|GPIO_PIN12) +#define BROADCOM_AFBR_S50_S2PI_IRQ /* PB4 */ (GPIO_INPUT|GPIO_PULLUP|GPIO_PORTB|GPIO_PIN4|GPIO_EXTI) +#define BROADCOM_AFBR_S50_S2PI_CLK /* PB10 */ GPIO_SPI2_SCK_1 +#define BROADCOM_AFBR_S50_S2PI_MOSI /* PB15 */ GPIO_SPI2_MOSI_1 +#define BROADCOM_AFBR_S50_S2PI_MISO /* PB14 */ GPIO_SPI2_MISO_1 + #define BOARD_HAS_CONTROL_STATUS_LEDS 1 #define BOARD_OVERLOAD_LED LED_RED #define BOARD_ARMED_STATE_LED LED_BLUE diff --git a/platforms/nuttx/src/px4/nxp/imxrt/board_hw_info/board_hw_rev_ver.c b/platforms/nuttx/src/px4/nxp/imxrt/board_hw_info/board_hw_rev_ver.c index f0097cb967..d4768fdd1e 100644 --- a/platforms/nuttx/src/px4/nxp/imxrt/board_hw_info/board_hw_rev_ver.c +++ b/platforms/nuttx/src/px4/nxp/imxrt/board_hw_info/board_hw_rev_ver.c @@ -160,7 +160,7 @@ static int read_id_dn(int *id, uint32_t gpio_drive, uint32_t gpio_sense, int adc /* Turn the sense lines to digital outputs LOW */ - imxrt_config_gpio(PX4_MAKE_GPIO_OUTPUT(gpio_sense)); + imxrt_config_gpio(PX4_MAKE_GPIO_OUTPUT_CLEAR(gpio_sense)); up_udelay(100); /* About 10 TC assuming 485 K */ @@ -172,7 +172,7 @@ static int read_id_dn(int *id, uint32_t gpio_drive, uint32_t gpio_sense, int adc /* Write the sense lines HIGH */ - imxrt_gpio_write(PX4_MAKE_GPIO_OUTPUT(gpio_sense), 1); + imxrt_gpio_write(PX4_MAKE_GPIO_OUTPUT_CLEAR(gpio_sense), 1); up_udelay(100); /* About 10 TC assuming 485 K */ diff --git a/platforms/nuttx/src/px4/nxp/k66/include/px4_arch/micro_hal.h b/platforms/nuttx/src/px4/nxp/k66/include/px4_arch/micro_hal.h index 0a870b6a96..0d240f0069 100644 --- a/platforms/nuttx/src/px4/nxp/k66/include/px4_arch/micro_hal.h +++ b/platforms/nuttx/src/px4/nxp/k66/include/px4_arch/micro_hal.h @@ -113,6 +113,7 @@ int kinetis_gpiosetevent(uint32_t pinset, bool risingedge, bool fallingedge, boo #define _PX4_MAKE_GPIO(pin_ftmx, io) ((((uint32_t)(pin_ftmx)) & ~(_PIN_MODE_MASK | _PIN_OPTIONS_MASK)) |(io)) #define PX4_MAKE_GPIO_INPUT(gpio) _PX4_MAKE_GPIO(gpio, GPIO_PULLUP) -#define PX4_MAKE_GPIO_OUTPUT(gpio) _PX4_MAKE_GPIO(gpio, GPIO_HIGHDRIVE) +#define PX4_MAKE_GPIO_OUTPUT_SET(gpio) _PX4_MAKE_GPIO(gpio, GPIO_HIGHDRIVE) +#define PX4_MAKE_GPIO_OUTPUT_CLEAR(gpio) _PX4_MAKE_GPIO(gpio, GPIO_LOWDRIVE) __END_DECLS diff --git a/platforms/nuttx/src/px4/nxp/rt106x/include/px4_arch/micro_hal.h b/platforms/nuttx/src/px4/nxp/rt106x/include/px4_arch/micro_hal.h index 368271913c..f319c80b68 100644 --- a/platforms/nuttx/src/px4/nxp/rt106x/include/px4_arch/micro_hal.h +++ b/platforms/nuttx/src/px4/nxp/rt106x/include/px4_arch/micro_hal.h @@ -105,6 +105,7 @@ int imxrt_gpiosetevent(uint32_t pinset, bool risingedge, bool fallingedge, bool #define px4_arch_gpiosetevent(pinset,r,f,e,fp,a) imxrt_gpiosetevent(pinset,r,f,e,fp,a) #define PX4_MAKE_GPIO_INPUT(gpio) (((gpio) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_INPUT | IOMUX_SCHMITT_TRIGGER | IOMUX_PULL_UP_47K | IOMUX_DRIVE_HIZ)) -#define PX4_MAKE_GPIO_OUTPUT(gpio) (((gpio) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_OUTPUT | GPIO_OUTPUT_ZERO | IOMUX_CMOS_OUTPUT | IOMUX_PULL_KEEP | IOMUX_DRIVE_33OHM | IOMUX_SPEED_MEDIUM | IOMUX_SLEW_FAST)) +#define PX4_MAKE_GPIO_OUTPUT_CLEAR(gpio) (((gpio) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_OUTPUT | GPIO_OUTPUT_ZERO | IOMUX_CMOS_OUTPUT | IOMUX_PULL_KEEP | IOMUX_DRIVE_33OHM | IOMUX_SPEED_MEDIUM | IOMUX_SLEW_FAST)) +#define PX4_MAKE_GPIO_OUTPUT_SET(gpio) (((gpio) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_OUTPUT | GPIO_OUTPUT_ONE | IOMUX_CMOS_OUTPUT | IOMUX_PULL_KEEP | IOMUX_DRIVE_33OHM | IOMUX_SPEED_MEDIUM | IOMUX_SLEW_FAST)) __END_DECLS diff --git a/platforms/nuttx/src/px4/stm/stm32_common/board_hw_info/board_hw_rev_ver.c b/platforms/nuttx/src/px4/stm/stm32_common/board_hw_info/board_hw_rev_ver.c index c6a7ca5492..c5b8ae2cac 100644 --- a/platforms/nuttx/src/px4/stm/stm32_common/board_hw_info/board_hw_rev_ver.c +++ b/platforms/nuttx/src/px4/stm/stm32_common/board_hw_info/board_hw_rev_ver.c @@ -170,7 +170,7 @@ static int read_id_dn(int *id, uint32_t gpio_drive, uint32_t gpio_sense, int adc /* Turn the sense lines to digital outputs LOW */ - stm32_configgpio(PX4_MAKE_GPIO_OUTPUT(gpio_sense)); + stm32_configgpio(PX4_MAKE_GPIO_OUTPUT_CLEAR(gpio_sense)); up_udelay(100); /* About 10 TC assuming 485 K */ @@ -182,7 +182,7 @@ static int read_id_dn(int *id, uint32_t gpio_drive, uint32_t gpio_sense, int adc /* Write the sense lines HIGH */ - stm32_gpiowrite(PX4_MAKE_GPIO_OUTPUT(gpio_sense), 1); + stm32_gpiowrite(PX4_MAKE_GPIO_OUTPUT_CLEAR(gpio_sense), 1); up_udelay(100); /* About 10 TC assuming 485 K */ diff --git a/platforms/nuttx/src/px4/stm/stm32_common/include/px4_arch/micro_hal.h b/platforms/nuttx/src/px4/stm/stm32_common/include/px4_arch/micro_hal.h index 63803b5a4a..18fbd4bf27 100644 --- a/platforms/nuttx/src/px4/stm/stm32_common/include/px4_arch/micro_hal.h +++ b/platforms/nuttx/src/px4/stm/stm32_common/include/px4_arch/micro_hal.h @@ -103,7 +103,8 @@ __BEGIN_DECLS #define px4_arch_gpiosetevent(pinset,r,f,e,fp,a) stm32_gpiosetevent(pinset,r,f,e,fp,a) #define PX4_MAKE_GPIO_INPUT(gpio) (((gpio) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_INPUT|GPIO_PULLUP)) -#define PX4_MAKE_GPIO_OUTPUT(gpio) (((gpio) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_2MHz|GPIO_OUTPUT_CLEAR)) +#define PX4_MAKE_GPIO_OUTPUT_CLEAR(gpio) (((gpio) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_2MHz|GPIO_OUTPUT_CLEAR)) +#define PX4_MAKE_GPIO_OUTPUT_SET(gpio) (((gpio) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_2MHz|GPIO_OUTPUT_SET)) #define PX4_GPIO_PIN_OFF(def) (((def) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | (GPIO_INPUT|GPIO_FLOAT|GPIO_SPEED_2MHz)) diff --git a/src/drivers/distance_sensor/CMakeLists.txt b/src/drivers/distance_sensor/CMakeLists.txt index 1c9e783799..e7e3b632f6 100644 --- a/src/drivers/distance_sensor/CMakeLists.txt +++ b/src/drivers/distance_sensor/CMakeLists.txt @@ -31,6 +31,7 @@ # ############################################################################ +add_subdirectory(broadcom) add_subdirectory(cm8jl65) add_subdirectory(leddar_one) add_subdirectory(ll40ls) diff --git a/src/drivers/distance_sensor/broadcom/CMakeLists.txt b/src/drivers/distance_sensor/broadcom/CMakeLists.txt new file mode 100644 index 0000000000..c47ae8fb22 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/CMakeLists.txt @@ -0,0 +1,34 @@ +############################################################################ +# +# Copyright (c) 2021 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. +# +############################################################################ + +#add_subdirectory(afbrs50) # not suitable for general inclusion diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.cpp b/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.cpp new file mode 100644 index 0000000000..8cb6355062 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.cpp @@ -0,0 +1,360 @@ +/**************************************************************************** + * + * Copyright (c) 2021 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. + * + ****************************************************************************/ + +/* Include Files */ +#include "AFBRS50.hpp" + +#include + +#include +#include + +#define AFBRS50_MEASURE_INTERVAL (1000000 / 100) // 10Hz + +/*! Define the SPI baud rate (to be used in the SPI module). */ +#define SPI_BAUD_RATE 5000000 + +#include "s2pi.h" +#include "timer.h" +#include "argus_hal_test.h" + +AFBRS50 *g_dev{nullptr}; + +AFBRS50::AFBRS50(uint8_t device_orientation): + ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::hp_default), + _px4_rangefinder(0, device_orientation) +{ + device::Device::DeviceId device_id{}; + device_id.devid_s.bus_type = device::Device::DeviceBusType::DeviceBusType_SPI; + device_id.devid_s.bus = BROADCOM_AFBR_S50_S2PI_SPI_BUS; + device_id.devid_s.devtype = DRV_DIST_DEVTYPE_AFBRS50; + + _px4_rangefinder.set_device_id(device_id.devid); +} + +AFBRS50::~AFBRS50() +{ + stop(); + + perf_free(_sample_perf); +} + +status_t AFBRS50::measurement_ready_callback(status_t status, void *data) +{ + if (!up_interrupt_context()) { + if (status == STATUS_OK) { + if (g_dev) { + g_dev->ProcessMeasurement(data); + } + } + } + + return status; +} + +void AFBRS50::ProcessMeasurement(void *data) +{ + if (data != nullptr) { + perf_count(_sample_perf); + + argus_results_t res{}; + status_t evaluate_status = Argus_EvaluateData(_hnd, &res, data); + + if ((evaluate_status == STATUS_OK) && (res.Status == 0)) { + uint32_t result_mm = res.Bin.Range / (Q9_22_ONE / 1000); + float result_m = static_cast(result_mm) / 1000.f; + _px4_rangefinder.update(((res.TimeStamp.sec * 1000000ULL) + res.TimeStamp.usec), result_m); + } + } +} + +int AFBRS50::init() +{ + if (_hnd != nullptr) { + // retry + Argus_Deinit(_hnd); + Argus_DestroyHandle(_hnd); + _hnd = nullptr; + } + + _hnd = Argus_CreateHandle(); + + if (_hnd == nullptr) { + PX4_ERR("Handle not initialized"); + return PX4_ERROR; + } + + // 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); + + if (status == STATUS_OK) { + uint32_t id = Argus_GetChipID(_hnd); + uint32_t value = Argus_GetAPIVersion(); + uint8_t a = (value >> 24) & 0xFFU; + uint8_t b = (value >> 16) & 0xFFU; + uint8_t c = value & 0xFFFFU; + PX4_INFO_RAW("AFBR-S50 Chip ID: %d, API Version: %d v%d.%d.%d\n", id, value, a, b, c); + + argus_module_version_t mv = Argus_GetModuleVersion(_hnd); + + switch (mv) { + case AFBR_S50MV85G_V1: + + // FALLTHROUGH + case AFBR_S50MV85G_V2: + + // FALLTHROUGH + case AFBR_S50MV85G_V3: + _px4_rangefinder.set_min_distance(0.08f); + _px4_rangefinder.set_max_distance(10.f); + _px4_rangefinder.set_fov(math::radians(6.f)); + PX4_INFO_RAW("AFBR-S50MV85G\n"); + break; + + case AFBR_S50LV85D_V1: + _px4_rangefinder.set_min_distance(0.08f); + _px4_rangefinder.set_max_distance(30.f); + _px4_rangefinder.set_fov(math::radians(6.f)); + PX4_INFO_RAW("AFBR-S50LV85D (v1)\n"); + break; + + case AFBR_S50MV68B_V1: + _px4_rangefinder.set_min_distance(0.08f); + _px4_rangefinder.set_max_distance(10.f); + _px4_rangefinder.set_fov(math::radians(1.f)); + PX4_INFO_RAW("AFBR-S50MV68B (v1)\n"); + break; + + case AFBR_S50MV85I_V1: + _px4_rangefinder.set_min_distance(0.08f); + _px4_rangefinder.set_max_distance(5.f); + _px4_rangefinder.set_fov(math::radians(6.f)); + PX4_INFO_RAW("AFBR-S50MV85I (v1)\n"); + break; + + case AFBR_S50SV85K_V1: + _px4_rangefinder.set_min_distance(0.08f); + _px4_rangefinder.set_max_distance(10.f); + _px4_rangefinder.set_fov(math::radians(4.f)); + PX4_INFO_RAW("AFBR-S50SV85K (v1)\n"); + break; + + default: + break; + } + + _state = STATE::CONFIGURE; + ScheduleDelayed(AFBRS50_MEASURE_INTERVAL); + return PX4_OK; + } + + return PX4_ERROR; +} + +void AFBRS50::Run() +{ + // backup schedule + ScheduleDelayed(100_ms); + + switch (_state) { + case STATE::TEST: { + Argus_VerifyHALImplementation(Argus_GetSPISlave(_hnd)); + + _state = STATE::CONFIGURE; + ScheduleDelayed(100_ms); + } + break; + + case STATE::CONFIGURE: { + Argus_SetConfigurationFrameTime(_hnd, AFBRS50_MEASURE_INTERVAL); + + status_t status = Argus_StartMeasurementTimer(_hnd, measurement_ready_callback); + + if (status != STATUS_OK) { + PX4_ERR("CONFIGURE status not okay: %i", status); + _state = STATE::STOP; + ScheduleNow(); + + } else { + _state = STATE::COLLECT; + ScheduleDelayed(AFBRS50_MEASURE_INTERVAL); + } + } + break; + + case STATE::COLLECT: { + // currently handeled by measurement_ready_callback + } + break; + + case STATE::STOP: { + Argus_StopMeasurementTimer(_hnd); + Argus_Deinit(_hnd); + Argus_DestroyHandle(_hnd); + } + break; + + default: + break; + } +} + +void AFBRS50::stop() +{ + _state = STATE::STOP; + ScheduleNow(); +} + +void AFBRS50::print_info() +{ + perf_print_counter(_sample_perf); +} + +namespace afbrs50 +{ + +static int start(const uint8_t rotation) +{ + if (g_dev != nullptr) { + PX4_ERR("already started"); + return PX4_ERROR; + } + + g_dev = new AFBRS50(rotation); + + if (g_dev == nullptr) { + PX4_ERR("object instantiate failed"); + return PX4_ERROR; + } + + // Initialize the sensor. + if (g_dev->init() != PX4_OK) { + PX4_ERR("driver start failed"); + delete g_dev; + g_dev = nullptr; + return PX4_ERROR; + } + + return PX4_OK; +} + +static int status() +{ + if (g_dev == nullptr) { + PX4_ERR("driver not running"); + return PX4_ERROR; + } + + g_dev->print_info(); + + return PX4_OK; +} + +static int stop() +{ + if (g_dev != nullptr) { + delete g_dev; + g_dev = nullptr; + + } + + PX4_INFO("driver stopped"); + return PX4_OK; +} + +static int usage() +{ + PRINT_MODULE_DESCRIPTION( + R"DESCR_STR( +### Description + +Driver for the Broadcom AFBRS50. + +### Examples + +Attempt to start driver on a specified serial device. +$ afbrs50 start +Stop driver +$ afbrs50 stop +)DESCR_STR"); + + PRINT_MODULE_USAGE_NAME("afbrs50", "driver"); + PRINT_MODULE_USAGE_SUBCATEGORY("distance_sensor"); + PRINT_MODULE_USAGE_COMMAND_DESCR("start", "Start driver"); + PRINT_MODULE_USAGE_PARAM_STRING('d', nullptr, nullptr, "Serial device", false); + PRINT_MODULE_USAGE_PARAM_INT('r', 25, 0, 25, "Sensor rotation - downward facing by default", true); + PRINT_MODULE_USAGE_COMMAND_DESCR("stop", "Stop driver"); + return PX4_OK; +} + +} // namespace + +extern "C" __EXPORT int afbrs50_main(int argc, char *argv[]) +{ + const char *myoptarg = nullptr; + + int ch = 0; + int myoptind = 1; + + uint8_t rotation = distance_sensor_s::ROTATION_DOWNWARD_FACING; + + while ((ch = px4_getopt(argc, argv, "d:r", &myoptind, &myoptarg)) != EOF) { + switch (ch) { + case 'r': + rotation = (uint8_t)atoi(myoptarg); + break; + + default: + PX4_WARN("Unknown option"); + return afbrs50::usage(); + } + } + + if (myoptind >= argc) { + return afbrs50::usage(); + } + + if (!strcmp(argv[myoptind], "start")) { + return afbrs50::start(rotation); + + } else if (!strcmp(argv[myoptind], "status")) { + return afbrs50::status(); + + } else if (!strcmp(argv[myoptind], "stop")) { + return afbrs50::stop(); + } + + return afbrs50::usage(); +} diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.hpp b/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.hpp new file mode 100644 index 0000000000..651dc709d3 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/AFBRS50.hpp @@ -0,0 +1,92 @@ +/**************************************************************************** + * + * Copyright (c) 2021 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** + * @file AFBRS50.hpp + * + * Driver for the Broadcom AFBR-S50 connected via SPI. + * + */ +#pragma once + +#include "argus.h" + +#include +#include +#include +#include +#include +#include + +using namespace time_literals; + +class AFBRS50 : public px4::ScheduledWorkItem +{ +public: + AFBRS50(const uint8_t device_orientation = distance_sensor_s::ROTATION_DOWNWARD_FACING); + ~AFBRS50() override; + + int init(); + + /** + * Diagnostics - print some basic information about the driver. + */ + void print_info(); + + /** + * Stop the automatic measurement state machine. + */ + void stop(); + +private: + void Run() override; + + void ProcessMeasurement(void *data); + + static status_t measurement_ready_callback(status_t status, void *data); + + argus_hnd_t *_hnd{nullptr}; + + enum class STATE : uint8_t { + TEST, + CONFIGURE, + COLLECT, + STOP + } _state{STATE::CONFIGURE}; + + PX4Rangefinder _px4_rangefinder; + + hrt_abstime _measurement_time{0}; + + perf_counter_t _sample_perf{perf_alloc(PC_INTERVAL, MODULE_NAME": sample interval")}; +}; diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/irq.h b/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/irq.h new file mode 100644 index 0000000000..6992ce34e4 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/irq.h @@ -0,0 +1,35 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details This file provides functionality to globally turn IRQs on/off. + * + * @copyright Copyright c 2016-2019, Avago Technologies GmbH. + * All rights reserved. + * + *****************************************************************************/ + +#ifndef IRQ_H +#define IRQ_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @defgroup IRQ IRQ: Global Interrupt Control + * @ingroup driver + * @brief Global IRQ Module + * @details This module provides functionality to globally enable/disable + * interrupts by turning the I-bit in the CPSR on/off. + * @addtogroup IRQ + * @{ + *****************************************************************************/ + +#include "platform/argus_irq.h" + +#ifdef __cplusplus +} +#endif + +/*! @} */ +#endif /* IRQ_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/s2pi.h b/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/s2pi.h new file mode 100644 index 0000000000..747d16f402 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/s2pi.h @@ -0,0 +1,107 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details This file provides an interface for the required S2PI module. + * + * @copyright Copyright c 2016-2019, Avago Technologies GmbH. + * All rights reserved. + * + *****************************************************************************/ + +#ifndef S2PI_H +#define S2PI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @defgroup SPI SPI: Serial Peripheral Interface + * @ingroup driver + * @brief S2PI: SPI incl. GPIO Hardware Layer Module + * + * @details Provides driver functionality for the S2PI interface module. + * + * 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. + * + * This module actually implements the #argus_s2pi interface that + * is required for the Argus API. Refer to the module for more + * information. + * + * @addtogroup SPI + * @{ + *****************************************************************************/ + +#include "platform/argus_s2pi.h" + + +/*! Enables the SPI slaves that utilize a GPIO pin for chip select. */ +#define S2PI_GPIO_SLAVES 0 + +/*! The S2PI slaves. */ +enum S2PISlaves { + /*! No SPI slave selected (all pins are disabled w/ high z state). */ + S2PI_NONE = 0, + + /*! The S2PI slave 1 (connected via adapter board). */ + S2PI_S1 = 1, + + /*! The S2PI slave 2 (connected via cable). */ + S2PI_S2 = 2, + +#if S2PI_GPIO_SLAVES + + /*! The S2PI slave 3. */ + S2PI_S3 = 3, + + /*! The S2PI slave 4. */ + S2PI_S4 = 4, + + /*! The experimental S2PI slave 1 w/ GPIO CS + * (connected via adapter board). */ + S2PI_S1_GPIO = 5, + + /*! The experimental S2PI slave 2 w/ GPIO CS + * (connected via cable). */ + S2PI_S2_GPIO = 6, + +#endif + + /*! No SPI slave selected (all pins go to low state). */ + S2PI_PINS_LOW = 0xFFU, +}; + + +/*!*************************************************************************** + * @brief Initializes the S2PI module. + * + * @details Setup the board as a S2PI master, this also sets up up the S2PI pins. + * The SPI interface is initialized with the corresponding default + * SPI slave (i.e. CS and IRQ lines) and the default baud rate. + * + * @param defaultSlave The default SPI slave to be addressed right after + * module initialization. + * @param baudRate_Bps The default SPI baud rate in bauds-per-second. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t S2PI_Init(s2pi_slave_t defaultSlave, uint32_t baudRate_Bps); + +/*!*************************************************************************** + * @brief Sets the SPI baud rate in bps. + * @param baudRate_Bps The default SPI baud rate in bauds-per-second. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + * - #STATUS_OK on success + * - #ERROR_S2PI_INVALID_BAUD_RATE on invalid baud rate value. + *****************************************************************************/ +status_t S2PI_SetBaudRate(uint32_t baudRate_Bps); + +#ifdef __cplusplus +} +#endif + +/*! @} */ +#endif // S2PI_H diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/timer.h b/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/timer.h new file mode 100644 index 0000000000..538b8f8c2e --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/API/Inc/timer.h @@ -0,0 +1,52 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details This file provides driver functionality for PIT (periodic interrupt timer). + * + * @copyright Copyright c 2016-2019, Avago Technologies GmbH. + * All rights reserved. + * + *****************************************************************************/ + +#ifndef TIMER_H +#define TIMER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @defgroup timer Timer Module + * @ingroup driver + * @brief Timer Hardware Driver Module + * @details Provides driver functionality for the timer peripherals. + * This module actually implements the #argus_timer interface that + * is required for the Argus API. It contains two timing + * functionalities: A periodic interrupt/callback timer and + * an lifetime counter. + * + * Note that the current implementation only features a single + * callback timer interval and does not yet support the feature + * of multiple intervalls at a time. + * + * @addtogroup timer + * @{ + *****************************************************************************/ + +/******************************************************************************* + * Include Files + ******************************************************************************/ + +#include "platform/argus_timer.h" + +/*!*************************************************************************** + * @brief Initializes the timer hardware. + *****************************************************************************/ +void Timer_Init(void); + +#ifdef __cplusplus +} +#endif + +/*! @} */ +#endif /* TIMER_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/irq.c b/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/irq.c new file mode 100644 index 0000000000..833cf9d486 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/irq.c @@ -0,0 +1,24 @@ + +#include + +static volatile irqstate_t irqstate_flags; + +/*!*************************************************************************** +* @brief Enable IRQ Interrupts +* +* @return - +*****************************************************************************/ +void IRQ_UNLOCK(void) +{ + leave_critical_section(irqstate_flags); +} + +/*!*************************************************************************** +* @brief Disable IRQ Interrupts +* +* @return - +*****************************************************************************/ +void IRQ_LOCK(void) +{ + irqstate_flags = enter_critical_section(); +} diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/s2pi.c b/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/s2pi.c new file mode 100644 index 0000000000..7aac79a5e3 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/s2pi.c @@ -0,0 +1,431 @@ + + +#include "irq.h" +#include "s2pi.h" + +#include + +#include + +#include + +#include +#include + +#include + +/*! A structure to hold all internal data required by the S2PI module. */ +typedef struct { + /*! Determines the current driver status. */ + volatile status_t Status; + + /*! Determines the current S2PI slave. */ + volatile s2pi_slave_t Slave; + + /*! A callback function to be called after transfer/run mode is completed. */ + s2pi_callback_t Callback; + + /*! A parameter to be passed to the callback function. */ + void *CallbackData; + + /*! A callback function to be called after external interrupt is triggered. */ + s2pi_irq_callback_t IrqCallback; + + /*! A parameter to be passed to the interrupt callback function. */ + void *IrqCallbackData; + + struct spi_dev_s *spidev; + uint8_t *spi_tx_data; + uint8_t *spi_rx_data; + size_t spi_frame_size; + + /*! The mapping of the GPIO blocks and pins for this device. */ + const uint32_t GPIOs[ S2PI_IRQ + 1 ]; +} +s2pi_handle_t; + +s2pi_handle_t s2pi_ = { .GPIOs = { [ S2PI_CLK ] = BROADCOM_AFBR_S50_S2PI_CLK, + [ S2PI_CS ] = BROADCOM_AFBR_S50_S2PI_CS, + [ S2PI_MOSI ] = BROADCOM_AFBR_S50_S2PI_MOSI, + [ S2PI_MISO ] = BROADCOM_AFBR_S50_S2PI_MISO, + [ S2PI_IRQ ] = BROADCOM_AFBR_S50_S2PI_IRQ + } + }; + +static struct work_s broadcom_s2pi_transfer_work = {}; + +static perf_counter_t s2pi_transfer_perf = NULL; +static perf_counter_t s2pi_transfer_callback_perf = NULL; +static perf_counter_t s2pi_irq_callback_perf = NULL; + +/*!*************************************************************************** +* @brief Initialize the S2PI module. +* @details Setup the board as a S2PI master, this also sets up up the S2PI +* pins. +* The SPI interface is initialized with the corresponding default +* SPI slave (i.e. CS and IRQ lines) and the default baud rate. +* +* @param defaultSlave The default SPI slave to be addressed right after +* module initialization. +* @param baudRate_Bps The default SPI baud rate in bauds-per-second. +* +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +*****************************************************************************/ + +static int gpio_falling_edge(int irq, void *context, void *arg) +{ + if (s2pi_.IrqCallback != 0) { + perf_begin(s2pi_irq_callback_perf); + s2pi_.IrqCallback(s2pi_.IrqCallbackData); + perf_end(s2pi_irq_callback_perf); + } + + return 0; +} + +status_t S2PI_Init(s2pi_slave_t defaultSlave, uint32_t baudRate_Bps) +{ + px4_arch_configgpio(BROADCOM_AFBR_S50_S2PI_CS); + + s2pi_.spidev = px4_spibus_initialize(BROADCOM_AFBR_S50_S2PI_SPI_BUS); + + px4_arch_configgpio(BROADCOM_AFBR_S50_S2PI_IRQ); + px4_arch_gpiosetevent(BROADCOM_AFBR_S50_S2PI_IRQ, false, true, false, &gpio_falling_edge, NULL); + + s2pi_transfer_perf = perf_alloc(PC_ELAPSED, MODULE_NAME": transfer"); + s2pi_transfer_callback_perf = perf_alloc(PC_ELAPSED, MODULE_NAME": transfer callback"); + s2pi_irq_callback_perf = perf_alloc(PC_ELAPSED, MODULE_NAME": irq callback"); + + return S2PI_SetBaudRate(baudRate_Bps); +} + +/*!*************************************************************************** +* @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. +*****************************************************************************/ +status_t S2PI_GetStatus(void) +{ + return s2pi_.Status; +} + +/*!*************************************************************************** +* @brief Sets the SPI baud rate in bps. +* @param baudRate_Bps The default SPI baud rate in bauds-per-second. +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +* - #STATUS_OK on success +* - #ERROR_S2PI_INVALID_BAUD_RATE on invalid baud rate value. +*****************************************************************************/ +status_t S2PI_SetBaudRate(uint32_t baudRate_Bps) +{ + SPI_SETMODE(s2pi_.spidev, SPIDEV_MODE3); + SPI_SETBITS(s2pi_.spidev, 8); + SPI_SETFREQUENCY(s2pi_.spidev, baudRate_Bps); + return STATUS_OK; +} + +/*!***************************************************************************** +* @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. +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +*****************************************************************************/ +status_t S2PI_CaptureGpioControl(void) +{ + /* Check if something is ongoing. */ + IRQ_LOCK(); + status_t status = s2pi_.Status; + + if (status != STATUS_IDLE) { + IRQ_UNLOCK(); + return status; + } + + s2pi_.Status = STATUS_S2PI_GPIO_MODE; + IRQ_UNLOCK(); + + // GPIO mode (output push pull) + px4_arch_configgpio(PX4_MAKE_GPIO_OUTPUT_SET(s2pi_.GPIOs[S2PI_CLK])); + px4_arch_configgpio(PX4_MAKE_GPIO_OUTPUT_SET(s2pi_.GPIOs[S2PI_MISO])); + px4_arch_configgpio(PX4_MAKE_GPIO_OUTPUT_SET(s2pi_.GPIOs[S2PI_MOSI])); + + return STATUS_OK; +} + +/*!***************************************************************************** +* @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. +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +*****************************************************************************/ +status_t S2PI_ReleaseGpioControl(void) +{ + /* Check if something is ongoing. */ + IRQ_LOCK(); + status_t status = s2pi_.Status; + + if (status != STATUS_S2PI_GPIO_MODE) { + IRQ_UNLOCK(); + return status; + } + + s2pi_.Status = STATUS_IDLE; + IRQ_UNLOCK(); + + // SPI alternate + stm32_configgpio(s2pi_.GPIOs[S2PI_CLK]); + stm32_configgpio(s2pi_.GPIOs[S2PI_MISO]); + stm32_configgpio(s2pi_.GPIOs[S2PI_MOSI]); + + // probably not necessary + stm32_spibus_initialize(BROADCOM_AFBR_S50_S2PI_SPI_BUS); + + return STATUS_OK; +} + +/*!***************************************************************************** +* @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. +* @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) +{ + /* Check if pin is valid. */ + if (pin > S2PI_IRQ || value > 1) { + return ERROR_INVALID_ARGUMENT; + } + + /* Check if in GPIO mode. */ + if (s2pi_.Status != STATUS_S2PI_GPIO_MODE) { + return ERROR_S2PI_INVALID_STATE; + } + + px4_arch_gpiowrite(s2pi_.GPIOs[pin], value); + + return STATUS_OK; +} + +/*!***************************************************************************** +* @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. +* @param slave The specified S2PI slave. +* @param pin The specified S2PI pin. +* @param value The GPIO pin state to read (0 = low, 1 = high). +* @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) +{ + /* Check if pin is valid. */ + if (pin > S2PI_IRQ || !value) { + return ERROR_INVALID_ARGUMENT; + } + + /* Check if in GPIO mode. */ + if (s2pi_.Status != STATUS_S2PI_GPIO_MODE) { + return ERROR_S2PI_INVALID_STATE; + } + + *value = px4_arch_gpioread(s2pi_.GPIOs[pin]); + + return STATUS_OK; +} + +/*!*************************************************************************** +* @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. +* @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) +{ + /* Check the driver status. */ + IRQ_LOCK(); + status_t status = s2pi_.Status; + + if (status != STATUS_IDLE) { + IRQ_UNLOCK(); + return status; + } + + s2pi_.Status = STATUS_BUSY; + IRQ_UNLOCK(); + + px4_arch_gpiowrite(s2pi_.GPIOs[S2PI_CS], 0); + px4_arch_gpiowrite(s2pi_.GPIOs[S2PI_CS], 1); + + s2pi_.Status = STATUS_IDLE; + + return STATUS_OK; +} + +/*!*************************************************************************** +* @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. +* The transfer of a single frame requires to not toggle the chip +* select line to high in between the data frame. +* An optional callback is invoked when the asynchronous transfer +* is finished. 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. +* +* @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 +* 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. +*****************************************************************************/ + +static void broadcom_s2pi_transfer_callout(void *arg) +{ + perf_begin(s2pi_transfer_perf); + px4_arch_gpiowrite(s2pi_.GPIOs[S2PI_CS], 0); + SPI_EXCHANGE(s2pi_.spidev, s2pi_.spi_tx_data, s2pi_.spi_rx_data, s2pi_.spi_frame_size); + s2pi_.Status = STATUS_IDLE; + px4_arch_gpiowrite(s2pi_.GPIOs[S2PI_CS], 1); + perf_end(s2pi_transfer_perf); + + /* Invoke callback if there is one */ + if (s2pi_.Callback != 0) { + perf_begin(s2pi_transfer_callback_perf); + s2pi_callback_t callback = s2pi_.Callback; + s2pi_.Callback = 0; + callback(STATUS_OK, s2pi_.CallbackData); + perf_end(s2pi_transfer_callback_perf); + } +} + +status_t S2PI_TransferFrame(s2pi_slave_t spi_slave, uint8_t const *txData, uint8_t *rxData, size_t frameSize, + s2pi_callback_t callback, void *callbackData) +{ + /* Verify arguments. */ + if (!txData || frameSize == 0 || frameSize >= 0x10000) { + return ERROR_INVALID_ARGUMENT; + } + + /* Check the spi slave.*/ + if (spi_slave != S2PI_S2) { + return ERROR_S2PI_INVALID_SLAVE; + } + + /* Check the driver status, lock if idle. */ + IRQ_LOCK(); + status_t status = s2pi_.Status; + + if (status != STATUS_IDLE) { + IRQ_UNLOCK(); + return status; + } + + s2pi_.Status = STATUS_BUSY; + + /* Set the callback information */ + s2pi_.Callback = callback; + s2pi_.CallbackData = callbackData; + + s2pi_.spi_tx_data = (uint8_t *)txData; + s2pi_.spi_rx_data = rxData; + s2pi_.spi_frame_size = frameSize; + work_queue(HPWORK, &broadcom_s2pi_transfer_work, broadcom_s2pi_transfer_callout, NULL, 0); + + IRQ_UNLOCK(); + + return STATUS_OK; +} + +/*!*************************************************************************** +* @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. +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +*****************************************************************************/ +status_t S2PI_Abort(void) +{ + status_t status = s2pi_.Status; + + /* Check if something is ongoing. */ + if (status == STATUS_IDLE) { + return STATUS_OK; + } + + /* Abort SPI transfer. */ + if (status == STATUS_BUSY) { + work_cancel(HPWORK, &broadcom_s2pi_transfer_work); + } + + return STATUS_OK; +} + +/*!*************************************************************************** +* @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 +* 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. +*****************************************************************************/ +status_t S2PI_SetIrqCallback(s2pi_slave_t slave, s2pi_irq_callback_t callback, void *callbackData) +{ + s2pi_.IrqCallback = callback; + s2pi_.IrqCallbackData = callbackData; + + return STATUS_OK; +} + +/*!*************************************************************************** +* @brief Reads the current 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. +* +* 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! +* @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). +*****************************************************************************/ +uint32_t S2PI_ReadIrqPin(s2pi_slave_t slave) +{ + return px4_arch_gpioread(s2pi_.GPIOs[S2PI_IRQ]); +} diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/timer.c b/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/timer.c new file mode 100644 index 0000000000..d627f0490b --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/API/Src/timer.c @@ -0,0 +1,138 @@ + +#include "timer.h" + +#include + +#include + +#include + +static struct hrt_call broadcom_hrt_call = {}; + +static timer_cb_t timer_callback_; /*! Callback function for PIT timer */ + +static uint32_t period_us_; + +/*! Storage for the callback parameter */ +static void *callback_param_; + +static void broadcom_hrt_callout(void *arg) +{ + if (timer_callback_ != 0) { + //timer_callback_(arg); + timer_callback_(callback_param_); + hrt_call_after(&broadcom_hrt_call, period_us_, broadcom_hrt_callout, callback_param_); + } +} + +void Timer_Init(void) +{ + hrt_cancel(&broadcom_hrt_call); +} + +/*!*************************************************************************** +* @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: +* +* t_now [usec] = hct * 1000000 usec + lct * 1 usec +* +* @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 usec +* @return - +*****************************************************************************/ + +void Timer_GetCounterValue(uint32_t *hct, uint32_t *lct) +{ + hrt_abstime time = hrt_absolute_time(); + *hct = time / 1000000ULL; + *lct = time - (*hct * 1000000ULL); +} + +/*!*************************************************************************** +* @brief Starts the timer 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. +* Passing a interval of 0 disables the timer. +* @param dt_microseconds The callback interval in microseconds. +* @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_Start(uint32_t period, void *param) +{ + callback_param_ = param; + period_us_ = period; + + if (period != 0) { + hrt_call_after(&broadcom_hrt_call, period, broadcom_hrt_callout, param); + + } else { + hrt_cancel(&broadcom_hrt_call); + } + + return STATUS_OK; +} + +/*!*************************************************************************** +* @brief Stops the timer for a specified callback parameter. +* @details Stops a callback interval for the specified parameter. +* @param param An abstract parameter that identifies the interval to be stopped. +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +*****************************************************************************/ +status_t Timer_Stop(void *param) +{ + period_us_ = 0; + callback_param_ = 0; + hrt_cancel(&broadcom_hrt_call); + return STATUS_OK; +} + +/*!*************************************************************************** +* @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. +* @param dt_microseconds The callback interval in microseconds. +* @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) +{ + if (dt_microseconds != 0) { + period_us_ = dt_microseconds; + hrt_call_after(&broadcom_hrt_call, dt_microseconds, broadcom_hrt_callout, param); + + } else { + hrt_cancel(&broadcom_hrt_call); + } + + return STATUS_OK; +} + +/*!*************************************************************************** +* @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. +* @param f The timer callback function. +* @return Returns the \link #status_t status\endlink (#STATUS_OK on success). +*****************************************************************************/ + +status_t Timer_SetCallback(timer_cb_t f) +{ + timer_callback_ = f; + return STATUS_OK; +} diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/CMakeLists.txt b/src/drivers/distance_sensor/broadcom/afbrs50/CMakeLists.txt new file mode 100644 index 0000000000..8073128881 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/CMakeLists.txt @@ -0,0 +1,59 @@ +############################################################################ +# +# Copyright (c) 2021 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. +# +############################################################################ + +add_library(afbrs50_m4_fpu STATIC IMPORTED GLOBAL) +set_property(TARGET afbrs50_m4_fpu PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/Lib/libafbrs50_m4_fpu.a) + +px4_add_module( + MODULE drivers__distance_sensor__afbrs50 + MAIN afbrs50 + COMPILE_FLAGS + STACK_MAIN + 4096 + INCLUDES + API/Inc + Inc + SRCS + AFBRS50.cpp + AFBRS50.hpp + API/Src/irq.c + API/Src/s2pi.c + API/Src/timer.c + argus_hal_test.c + DEPENDS + px4_work_queue + drivers_rangefinder + afbrs50_m4_fpu + ) + +target_link_libraries(afbrs50_m4_fpu INTERFACE drivers__distance_sensor__afbrs50) 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 new file mode 100644 index 0000000000..598b33375c --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_api.h @@ -0,0 +1,1185 @@ +/*************************************************************************//** + * @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. + * + * @copyright + * + * Copyright (c) 2021, 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_API_H +#define ARGUS_API_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @defgroup argusapi AFBR-S50 API + * + * @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. + * + * @addtogroup argusapi + * @{ + *****************************************************************************/ + +#include "argus_def.h" +#include "argus_res.h" +#include "argus_pba.h" +#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; + +/*! The S2PI slave identifier. */ +typedef int32_t s2pi_slave_t; + +/*!*************************************************************************** + * @brief Initializes the API modules and the device with default parameters. + * + * @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. + * + * 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 modules configuration is initialized with reasonable default values. + * + * @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. + * + * @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. + * + * @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. + * + * 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. + * + * @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. + * + * @details The function deinitializes the device and clear all internal states. + * Can be used to cleanup before releaseing 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. + * + * @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_Deinit(argus_hnd_t *hnd); + +/*!*************************************************************************** + * @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 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 + * Also see the #Argus_DestroyHandle method for the corresponding + * deallocation of the allocated memory. + * + * @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. + * + * @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. + * + * Please refer to the #Argus_CreateHandle method for the corresponding + * allocation of the memory. + * + * 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. + * + * @code void Argus_Free (void * ptr) @endcode + * + * @param hnd The device handle object to be deallocated. + *****************************************************************************/ +void Argus_DestroyHandle(argus_hnd_t *hnd); + +/*!************************************************************************** + * Generic API + ****************************************************************************/ + +/*!*************************************************************************** + * @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. + * + * The values are encoded into a 32-bit value: + * + * - [ 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: + * + * @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. + *****************************************************************************/ +uint32_t Argus_GetAPIVersion(void); + +/*!*************************************************************************** + * @brief Gets the build number of the current API library. + * + * @return Returns the current build number as a C-string. + *****************************************************************************/ +char const *Argus_GetBuildNumber(void); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +argus_module_version_t Argus_GetModuleVersion(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. + * + * @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. + * + * @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. + * + * @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); + +/*! @} */ + +/*!************************************************************************** + * Measurement/Device Operation + **************************************************************************** + * @addtogroup argusmeas + * @{ + ****************************************************************************/ + +/*!*************************************************************************** + * @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. When the frame is + * 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 cycle + * and it must not be invoked from the callback since it is within an + * interrupt service routine. Rather a flag should inform the main thread + * 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). + * 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 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. + * + * @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). + *****************************************************************************/ +status_t Argus_StartMeasurementTimer(argus_hnd_t *hnd, argus_callback_t cb); + +/*!*************************************************************************** + * @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. + * + * @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. + * + * @details This function immediately triggers a single measurement frame + * asynchronously if all the pre-conditions are met. Otherwise it returns + * with a corresponding status. + * When the frame is 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 from the callback since it is + * within an interrupt service routine. Rather a flag should inform + * the main thread to invoke the evaluation. + * 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 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). + *****************************************************************************/ +status_t Argus_TriggerMeasurement(argus_hnd_t *hnd, argus_callback_t cb); + +/*!*************************************************************************** + * @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. + * + * @details Returns the current module state: + * + * 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). + * . + * + * 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 occured) (== #ERROR_ARGUS_NOT_CONNECTED). + * - Timeout: A previous frame measurement has not finished within a + * specified time (== #ERROR_TIMEOUT). + * . + * + * @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. + * + * @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. + * + * @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. + * + * @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.\n + * 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. + * + * @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). + *****************************************************************************/ +status_t Argus_EvaluateData(argus_hnd_t *hnd, argus_results_t *res, void *raw); + +/*!*************************************************************************** + * @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. + * + * 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. + * + * @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_ExecuteXtalkCalibrationSequence(argus_hnd_t *hnd, argus_mode_t mode); + + +/*!*************************************************************************** + * @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. + * + * In order to perform a relative range offset calibration, a flat + * calibration target must be setup perpendicular to the sensors + * field-of-view. + * + * \code + * AFBR-S50 ToF Sensor + * #| + * #| | + * #|-----+ | + * #| 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. + * . + * + * 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 mode The targeted measurement mode. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_ExecuteRelativeRangeOffsetCalibrationSequence(argus_hnd_t *hnd, + argus_mode_t mode); + +/*!*************************************************************************** + * @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. + * + * In order to perform a relative range offset calibration, a flat + * calibration target must be setup perpendicular to the sensors + * field-of-view. + * + * \code + * AFBR-S50 ToF Sensor + * #| + * #| | + * #|-----+ | + * #| 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. + * . + * + * 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 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). + *****************************************************************************/ +status_t Argus_ExecuteAbsoluteRangeOffsetCalibrationSequence(argus_hnd_t *hnd, + argus_mode_t mode, + q9_22_t targetRange); + +/*! @} */ + +/*!************************************************************************** + * Configuration API + **************************************************************************** + * @addtogroup arguscfg + * @{ + ****************************************************************************/ + +/*!*************************************************************************** + * @brief Sets the measurement mode to a specified device. + * + * @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). + *****************************************************************************/ +status_t Argus_SetConfigurationMeasurementMode(argus_hnd_t *hnd, + argus_mode_t value); + +/*!*************************************************************************** + * @brief Gets the measurement mode from 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). + *****************************************************************************/ +status_t Argus_GetConfigurationMeasurementMode(argus_hnd_t *hnd, + argus_mode_t *value); + +/*!*************************************************************************** + * @brief Sets the frame time 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). + *****************************************************************************/ +status_t Argus_SetConfigurationFrameTime(argus_hnd_t *hnd, uint32_t value); + +/*!*************************************************************************** + * @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). + *****************************************************************************/ +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). + *****************************************************************************/ +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). + *****************************************************************************/ +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). + *****************************************************************************/ +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). + *****************************************************************************/ +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). + *****************************************************************************/ +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). + *****************************************************************************/ +status_t Argus_GetConfigurationShotNoiseMonitorMode(argus_hnd_t *hnd, + argus_mode_t mode, + argus_snm_mode_t *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). + *****************************************************************************/ +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. + * + * @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). + *****************************************************************************/ +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). + *****************************************************************************/ +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). + *****************************************************************************/ +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). + *****************************************************************************/ +status_t Argus_GetConfigurationUnambiguousRange(argus_hnd_t *hnd, + uint32_t *range_mm); + +/*! @} */ + +/*!************************************************************************** + * Calibration API + **************************************************************************** + * @addtogroup arguscal + * @{ + ****************************************************************************/ + +/*!*************************************************************************** + * @brief Sets the global range offset value to a specified device. + * + * @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 Q9.22 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, + q9_22_t value); + +/*!*************************************************************************** + * @brief Gets the global range offset value from a specified device. + * + * @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 Q9.22 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, + q9_22_t *value); + +/*!*************************************************************************** + * @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! + * + * The crosstalk vector table is a two dimensional array of type + * #q0_15_t. + * + * 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. + * + * If a constant offset table for all device needs to be incorporated + * into the sources, the #Argus_GetExternalPixelRangeOffsets_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). + *****************************************************************************/ +status_t Argus_SetCalibrationPixelRangeOffsets(argus_hnd_t *hnd, argus_mode_t mode, + q0_15_t value[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); + + +/*!*************************************************************************** + * @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! + * + * The crosstalk vector table is a two dimensional array of type + * #q0_15_t. + * + * 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). + *****************************************************************************/ +status_t Argus_GetCalibrationPixelRangeOffsets(argus_hnd_t *hnd, argus_mode_t mode, + q0_15_t value[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); + + +/*!*************************************************************************** + * @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! + * + * The crosstalk vector table is a two dimensional array of type + * #q0_15_t. + * + * 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). + *****************************************************************************/ +status_t Argus_GetCalibrationTotalPixelRangeOffsets(argus_hnd_t *hnd, argus_mode_t mode, + q0_15_t value[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]); + + +/*!*************************************************************************** + * @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. + * + * @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. + * + * 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: + * + * @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); + * + * // 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 + * + * @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. + *****************************************************************************/ +void Argus_GetExternalPixelRangeOffsets_Callback(q0_15_t offsets[ARGUS_PIXELS_X][ARGUS_PIXELS_Y], + argus_mode_t mode); + +/*!*************************************************************************** + * @brief Sets the sample count for the range offset calibration sequence. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The new range offset calibration sequence sample count. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_SetCalibrationRangeOffsetSequenceSampleCount(argus_hnd_t *hnd, uint16_t value); + +/*!*************************************************************************** + * @brief Gets the sample count for the range offset calibration sequence. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The current range offset calibration sequence sample count. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_GetCalibrationRangeOffsetSequenceSampleCount(argus_hnd_t *hnd, uint16_t *value); + +/*!*************************************************************************** + * @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). + *****************************************************************************/ +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. + * + * @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). + *****************************************************************************/ +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. + * + * @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) + * . + * + * 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. + * + * @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). + *****************************************************************************/ +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]); + +/*!*************************************************************************** + * @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. + * + * 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) + * . + * + * @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). + *****************************************************************************/ +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]); + +/*!*************************************************************************** + * @brief Gets the factory calibrated default crosstalk vector table for the + * specified device. + * + * @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. + * * + * 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). + *****************************************************************************/ +status_t Argus_ResetCalibrationCrosstalkVectorTable(argus_hnd_t *hnd, + argus_mode_t mode); + +/*!*************************************************************************** + * @brief Sets the sample count for the crosstalk calibration sequence. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The new crosstalk calibration sequence sample count. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_SetCalibrationCrosstalkSequenceSampleCount(argus_hnd_t *hnd, + uint16_t value); + +/*!*************************************************************************** + * @brief Gets the sample count for the crosstalk calibration sequence. + * + * @param hnd The API handle; contains all internal states and data. + * @param value The current crosstalk calibration sequence sample count. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Argus_GetCalibrationCrosstalkSequenceSampleCount(argus_hnd_t *hnd, + uint16_t *value); + +/*!*************************************************************************** + * @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. + * + * @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. + * + * @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). + *****************************************************************************/ +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. + * + * @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). + *****************************************************************************/ +status_t Argus_GetCalibrationVsubSequenceSampleCount(argus_hnd_t *hnd, + uint16_t *value); + +/*!*************************************************************************** + * @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. + * + * 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. + * + * 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)); + * + * // 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 + * + * @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. + *****************************************************************************/ +void Argus_GetExternalCrosstalkVectorTable_Callback(xtalk_t + xtalk[ARGUS_DFM_FRAME_COUNT][ARGUS_PIXELS_X][ARGUS_PIXELS_Y], + argus_mode_t mode); + + +#ifdef __cplusplus +} +#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 new file mode 100644 index 0000000000..f5f6845368 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dca.h @@ -0,0 +1,489 @@ +/*************************************************************************//** + * @file + * @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 + * 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_DCA_H +#define ARGUS_DCA_H + +/*!*************************************************************************** + * @defgroup argusdca Dynamic Configuration Adaption + * @ingroup argusapi + * + * @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. + * + * 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 + * @{ + *****************************************************************************/ + +#include "argus_def.h" + + + +/*! The minimum amplitude threshold value. */ +#define ARGUS_CFG_DCA_ATH_MIN (1U << 6U) + +/*! The maximum amplitude threshold value. */ +#define ARGUS_CFG_DCA_ATH_MAX (0xFFFFU) + + +/*! The minimum saturated pixel threshold value. */ +#define ARGUS_CFG_DCA_PXTH_MIN (1U) + +/*! The maximum saturated pixel threshold value. */ +#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))) + +/*! 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 1 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 1 high current in mA. */ +#define ARGUS_CFG_DCA_POWER_MIN_LSB (1) + +/*! The maximum optical output power, i.e. the maximum VCSEL 1 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 1 high current in mA. */ +#define ARGUS_CFG_DCA_POWER_MIN (1) + + + + + + + + +/*! The dynamic configuration algorithm Pixel Input Gain stage count. */ +#define ARGUS_DCA_GAIN_STAGE_COUNT (4U) + +/*! The dynamic configuration algorithm state mask for the Pixel Input Gain stage. */ +#define ARGUS_STATE_DCA_GAIN_MASK (0x03U) + +/*! The dynamic configuration algorithm state mask for the Pixel Input Gain stage. */ +#define ARGUS_STATE_DCA_GAIN_SHIFT (14U) + +/*! Getter for the dynamic configuration algorithm Pixel Input Gain stage. */ +#define ARGUS_STATE_DCA_GAIN_GET(state) \ + (((state) >> ARGUS_STATE_DCA_GAIN_SHIFT) & ARGUS_STATE_DCA_GAIN_MASK) + + +/*! The dynamic configuration algorithm Optical Output Power stage count. */ +#define ARGUS_DCA_POWER_STAGE_COUNT (4U) + +/*! The dynamic configuration algorithm state mask for the Optical Output Power stage. */ +#define ARGUS_STATE_DCA_POWER_MASK (0x03U) + +/*! The dynamic configuration algorithm state mask for the Optical Output Power stage. */ +#define ARGUS_STATE_DCA_POWER_SHIFT (12U) + +/*! Getter for the dynamic configuration algorithm Optical Output Power stage. */ +#define ARGUS_STATE_DCA_POWER_GET(state) \ + (((state) >> ARGUS_STATE_DCA_POWER_SHIFT) & ARGUS_STATE_DCA_POWER_MASK) + + +/*! The dynamic configuration algorithm state mask for the Max. Analog Integration Depth shift value. */ +#define ARGUS_STATE_DCA_DEPTH_SHFT_MASK (0x0FU) + +/*! The dynamic configuration algorithm state mask for the Max. Analog Integration Depth shift value. */ +#define ARGUS_STATE_DCA_DEPTH_SHFT_SHIFT (8U) + +/*! Getter for the dynamic configuration algorithm Max. Analog Integration Depth shift value. */ +#define ARGUS_STATE_DCA_DEPTH_SHFT_GET(state) \ + (((state) >> ARGUS_STATE_DCA_DEPTH_SHFT_SHIFT) & ARGUS_STATE_DCA_DEPTH_SHFT_MASK) + + +/*!*************************************************************************** + * @brief The dynamic configuration algorithm enable flags. + *****************************************************************************/ +typedef enum { + /*! DCA is disabled and will be completely skipped. */ + DCA_ENABLE_OFF = 0, + + /*! DCA is enabled and will dynamically adjust the device configuration. */ + DCA_ENABLE_DYNAMIC = 1, + + /*! DCA is enabled and will apply the static (nominal) values to the device. */ + DCA_ENABLE_STATIC = -1, + +} argus_dca_enable_t; + +/*!*************************************************************************** + * @brief The dynamic configuration algorithm Optical Output Power stages enumerator. + *****************************************************************************/ +typedef enum { + /*! Low output power stage. */ + DCA_POWER_LOW = 0, + + /*! Medium low output power stage. */ + DCA_POWER_MEDIUM_LOW = 1, + + /*! Medium high output power stage. */ + DCA_POWER_MEDIUM_HIGH = 2, + + /*! High output power stage. */ + DCA_POWER_HIGH = 3 + +} argus_dca_power_t; + +/*!*************************************************************************** + * @brief The dynamic configuration algorithm Pixel Input Gain stages enumerator. + *****************************************************************************/ +typedef enum { + /*! Low gain stage. */ + DCA_GAIN_LOW = 0, + + /*! Medium low gain stage. */ + DCA_GAIN_MEDIUM_LOW = 1, + + /*! Medium high gain stage. */ + DCA_GAIN_MEDIUM_HIGH = 2, + + /*! High gain stage. */ + DCA_GAIN_HIGH = 3 + +} argus_dca_gain_t; + + +/*!*************************************************************************** + * @brief State flags for the current frame. + * @details State flags determine the current state of the measurement frame: + * - [0]: #ARGUS_STATE_MEASUREMENT_MODE: Measurement Mode: + * - 0: Mode A + * - 1: Mode B + * . + * - [1]: #ARGUS_STATE_DUAL_FREQ_MODE: Dual Frequency Mode Enabled Flag + * - 0: Disabled, measurements w/ base frequency only + * - 1: Enabled, measurements w/ detuned frequency + * . + * - [2]: #ARGUS_STATE_MEASUREMENT_FREQ: Measurement Frequency for + * Dual Frequency Mode, (only valid if #ARGUS_STATE_DUAL_FREQ_MODE + * flag is set) + * - 0: A-Frame w/ detuned frequency + * - 1: B-Frame w/ detuned frequency + * . + * - [3]: #ARGUS_STATE_DEBUG_MODE + * - [4]: #ARGUS_STATE_GOLDEN_PIXEL_MODE + * - [5]: #ARGUS_STATE_BGL_WARNING + * - [6]: #ARGUS_STATE_BGL_ERROR + * - [7]: #ARGUS_STATE_PLL_LOCKED + * - 0: PLL_LOCKED bit was not set at start of integration; + * - 0: PLL_LOCKED bit was set at start of integration; + * . + * - [8-11]: Max. Depth Shift Value + * - [12-13]: Power Stages + * - [14-15]: Gain Stages + * . + *****************************************************************************/ +typedef enum { + /*! No state flag set. */ + ARGUS_STATE_NONE = 0, + + /*! 0x01: Measurement Mode. + * - 0: Mode A: Long Range / Medium Precision + * - 1: Mode B: Short Range / High Precision */ + ARGUS_STATE_MEASUREMENT_MODE = 1U << 0U, + + /*! 0x02: Dual Frequency Mode Enabled. + * - 0: Disabled: measurements with base frequency, + * - 1: Enabled: measurement with detuned frequency. */ + ARGUS_STATE_DUAL_FREQ_MODE = 1U << 1U, + + /*! 0x04: 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 */ + ARGUS_STATE_MEASUREMENT_FREQ = 1U << 2U, + + /*! 0x08: Debug Mode. If set, the range value of erroneous pixels are not + * cleared or reset. + * - 0: Disabled (default). + * - 1: Enabled. */ + ARGUS_STATE_DEBUG_MODE = 1U << 3U, + + /*! 0x10: Golden Pixel Mode Flag. + * Set whenever the Pixel Binning Algorithm is operating in the + * Golden Pixel Mode. + * - 0: Normal Pixel Binning Mode. + * - 1: Golden Pixel Mode. */ + ARGUS_STATE_GOLDEN_PIXEL_MODE = 1U << 4U, + + /*! 0x20: 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. */ + ARGUS_STATE_BGL_WARNING = 1U << 5U, + + /*! 0x40: 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. */ + ARGUS_STATE_BGL_ERROR = 1U << 6U, + + /*! 0x80: PLL_LOCKED bit. + * - 0: PLL not locked at start of integration. + * - 1: PLL locked at start of integration. */ + ARGUS_STATE_PLL_LOCKED = 1U << 7U, + + /*! DCA is in low Optical Output Power stage. */ + ARGUS_STATE_DCA_POWER_LOW = DCA_GAIN_LOW << ARGUS_STATE_DCA_POWER_SHIFT, + + /*! DCA is in medium-low Optical Output Power stage. */ + ARGUS_STATE_DCA_POWER_MED_LOW = DCA_GAIN_MEDIUM_LOW << ARGUS_STATE_DCA_POWER_SHIFT, + + /*! DCA is in medium-high Optical Output Power stage. */ + ARGUS_STATE_DCA_POWER_MED_HIGH = DCA_GAIN_MEDIUM_HIGH << ARGUS_STATE_DCA_POWER_SHIFT, + + /*! DCA is in high Optical Output Power stage. */ + ARGUS_STATE_DCA_POWER_HIGH = DCA_GAIN_HIGH << ARGUS_STATE_DCA_POWER_SHIFT, + + + /*! DCA is in low Pixel Input Gain stage. */ + ARGUS_STATE_DCA_GAIN_LOW = DCA_GAIN_LOW << ARGUS_STATE_DCA_GAIN_SHIFT, + + /*! DCA is in medium-low Pixel Input Gain stage. */ + ARGUS_STATE_DCA_GAIN_MED_LOW = DCA_GAIN_MEDIUM_LOW << ARGUS_STATE_DCA_GAIN_SHIFT, + + /*! DCA is in medium-high Pixel Input Gain stage. */ + ARGUS_STATE_DCA_GAIN_MED_HIGH = DCA_GAIN_MEDIUM_HIGH << ARGUS_STATE_DCA_GAIN_SHIFT, + + /*! DCA is in high Pixel Input Gain stage. */ + ARGUS_STATE_DCA_GAIN_HIGH = DCA_GAIN_HIGH << ARGUS_STATE_DCA_GAIN_SHIFT, + +} 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. + * . + *****************************************************************************/ +typedef struct { + /*! Enables the automatic configuration adaption features. + * Enables the dynamic part if #DCA_ENABLE_DYNAMIC and the static only if + * #DCA_ENABLE_STATIC. If set to DCA_ENABLE_OFF, the DCA is completely + * skipped and the static register values are considered which is + * recommended for advanced debugging only. */ + argus_dca_enable_t Enabled; + + /*! The threshold value of saturated pixels that causes a linear reduction + * of the integration energy, i.e. if the number of saturated pixels are + * larger or equal to this value, the integration energy will be reduced + * by a single step (one pattern if the current integration depth is > 1, + * one pulse if the current integration depth is <= 1 or one power LSB for + * the optical power range). + * + * Valid values: 1, ..., 33; (use 33 to disable the linear decrease) + * Note that the linear value must be smaller or equal to the exponential + * value. To sum up, it must hold: + * 1 <= SatPxThLin <= SatPxThExp <= SatPxThRst <= 33 */ + uint8_t SatPxThLin; + + /*! The threshold number of saturated pixels that causes a exponential + * reduction of the integration energy, i.e. if the number of saturated + * pixels is larger or equal to this value, the integration energy will be + * halved. + * + * Valid values: 1, ..., 33; (use 33 to disable the exponential decrease) + * Note that the exponential value must be between the linear and reset + * values. To sum up, it must hold: + * 1 <= SatPxThLin <= SatPxThExp <= SatPxThRst <= 33 */ + uint8_t SatPxThExp; + + /*! The threshold number of saturated pixels that causes a sudden reset of + * the integration energy to the minimal value, i.e. if the number of + * saturated pixels are larger or equal to this value, the integration + * energy will suddenly be reset to the minimum values. The gain setting + * will stay at the mid value and a decrease happens after the next step + * if still required. + * + * Valid values: 1, ..., 33; (use 33 to disable the sudden reset) + * Note that the reset value must be larger or equal to the exponential + * value. To sum up, it must hold: + * 1 <= SatPxThLin <= SatPxThExp <= SatPxThRst <= 33 */ + uint8_t SatPxThRst; + + /*! The amplitude to be targeted from the lower regime. If the amplitude + * lower than the target value, a linear increase of integration energy + * will happen in order to optimize for best performance. + * + * Valid values: #ARGUS_CFG_DCA_ATH_MIN, ... #ARGUS_CFG_DCA_ATH_MAX or 0 + * Set 0 to disable optimization toward the target amplitude. + * Note further that the following condition must hold: + * 'MIN' <= AthLow <= Atarget <= AthHigh <= 'MAX' */ + uq12_4_t Atarget; + + /*! The low threshold value for the max. amplitude. If the max. amplitude + * falls below this value, the integration depth will be increases. + * + * Valid values: #ARGUS_CFG_DCA_ATH_MIN, ... #ARGUS_CFG_DCA_ATH_MAX + * Note further that the following condition must hold: + * 'MIN' <= AthLow <= Atarget <= AthHigh <= 'MAX' */ + uq12_4_t AthLow; + + /*! The high threshold value for the max. amplitude. If the max. amplitude + * exceeds this value, the integration depth will be decreases. Note that + * also saturated pixels will cause a decrease of the integration depth. + * + * Valid values: #ARGUS_CFG_DCA_ATH_MIN, ... #ARGUS_CFG_DCA_ATH_MAX + * Note further that the following condition must hold: + * 'MIN' <= AthLow <= Atarget <= AthHigh <= 'MAX' */ + uq12_4_t AthHigh; + + /*! The nominal analog integration depth in UQ10.6 format, + * i.e. the nominal pattern count per sample. + * + * Valid values: #ARGUS_CFG_DCA_DEPTH_MIN, ... #ARGUS_CFG_DCA_DEPTH_MAX + * Note further that the following condition must hold: + * 'MIN' <= DepthLow <= DepthNom <= DepthHigh <= 'MAX' */ + uq10_6_t DepthNom; + + /*! The minimum analog integration depth in UQ10.6 format, + * i.e. the minimum pattern count per sample. + * + * Valid values: #ARGUS_CFG_DCA_DEPTH_MIN, ... #ARGUS_CFG_DCA_DEPTH_MAX + * Note further that the following condition must hold: + * 'MIN' <= DepthLow <= DepthNom <= DepthHigh <= 'MAX' */ + uq10_6_t DepthMin; + + /*! The maximum analog integration depth in UQ10.6 format, + * i.e. the maximum pattern count per sample. + * + * Valid values: #ARGUS_CFG_DCA_DEPTH_MIN, ... #ARGUS_CFG_DCA_DEPTH_MAX + * Note further that the following condition must hold: + * 'MIN' <= DepthMin <= DepthNom <= DepthMax <= 'MAX' */ + uq10_6_t DepthMax; + + /*! The nominal optical output power in mA, + * i.e. the nominal VCSEL_HC1 setting. + * + * Valid values: #ARGUS_CFG_DCA_POWER_MIN, ... #ARGUS_CFG_DCA_POWER_MAX + * Note further that the following condition must hold: + * 'MIN' <= PowerMin <= PowerNom <= 'MAX' */ + uq12_4_t PowerNom; + + /*! The minimum optical output power in mA, + * i.e. the minimum VCSEL_HC1 setting. + * + * Valid values: #ARGUS_CFG_DCA_POWER_MIN, ... #ARGUS_CFG_DCA_POWER_MAX + * Note further that the following condition must hold: + * 'MIN' <= PowerMin <= PowerNom <= 'MAX' */ + uq12_4_t PowerMin; + + /*! The nominal pixel gain setting, i.e. the setting for + * nominal/default gain stage. + * + * Valid values: 0,..,3: #DCA_GAIN_LOW, ... #DCA_GAIN_HIGH + * Note further that the following condition must hold: + * 'MIN' <= GainMin <= GainNom <= GainMax <= 'MAX' */ + argus_dca_gain_t GainNom; + + /*! The minimal pixel gain setting, i.e. the setting for + * minimum gain stage. + * + * Valid values: 0,..,3: #DCA_GAIN_LOW, ... #DCA_GAIN_HIGH + * Note further that the following condition must hold: + * 'MIN' <= GainMin <= GainNom <= GainMax <= 'MAX' */ + argus_dca_gain_t GainMin; + + /*! The maximum pixel gain setting, i.e. the setting for + * maximum gain stage. + * + * Valid values: 0,..,3: #DCA_GAIN_LOW, ... #DCA_GAIN_HIGH + * Note further that the following condition must hold: + * 'MIN' <= GainMin <= GainNom <= GainMax <= 'MAX' */ + argus_dca_gain_t GainMax; + + /*! Power Saving Ratio value. + * + * Determines the percentage of the full available frame time that is not + * exploited for digital integration. Thus the device is idle within the + * specified portion of the frame time and does consume less energy. + * + * Note that the laser safety might already limit the maximum integration + * depth and the power saving ratio might not take effect for all ambient + * situations. Thus the Power Saving Ratio is to be understood as a minimum + * percentage where the device is idle per frame. + * + * The value is a UQ0.8 format that ranges from 0.0 (=0x00) to 0.996 (=0xFF), + * where 0 means no power saving (i.e. feature disabled) and 0xFF determines + * maximum power saving, i.e. the digital integration depth is limited to a + * single sample. + * + * Range: 0x00, .., 0xFF; set 0 to disable. */ + uq0_8_t PowerSavingRatio; + +} argus_cfg_dca_t; + +/*! @} */ +#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 new file mode 100644 index 0000000000..4762e1d9cd --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_def.h @@ -0,0 +1,205 @@ +/*************************************************************************//** + * @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. + * + * @copyright + * + * Copyright (c) 2021, 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_DEF_H +#define ARGUS_DEF_H + +/*!*************************************************************************** + * Include files + *****************************************************************************/ +#include "argus_status.h" +#include "argus_version.h" +#include "utility/fp_def.h" +#include "utility/time.h" +#include +#include +#include +#include +#include + +/*!*************************************************************************** + * @addtogroup argusapi + * @{ + *****************************************************************************/ + +/*!*************************************************************************** + * @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 + +/*!*************************************************************************** + * @brief The device pixel field size in x direction (long edge). + *****************************************************************************/ +#define ARGUS_PIXELS_X 8U + +/*!*************************************************************************** + * @brief The device pixel field size in y direction (short edge). + *****************************************************************************/ +#define ARGUS_PIXELS_Y 4U + +/*!*************************************************************************** + * @brief The total device pixel count. + *****************************************************************************/ +#define ARGUS_PIXELS ((ARGUS_PIXELS_X)*(ARGUS_PIXELS_Y)) + +/*!*************************************************************************** + * @brief The AFBR-S50 module types. + *****************************************************************************/ +typedef enum { + /*! No device connected or not recognized. */ + MODULE_NONE = 0, + + /*! 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 1 - legacy version! */ + AFBR_S50MV85G_V1 = 1, + + /*! 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-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-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-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-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, + + + /*! Reserved for future extensions. */ + Reserved = 0b111111 + +} argus_module_version_t; + +/*!*************************************************************************** + * @brief The AFBR-S50 laser configurations. + *****************************************************************************/ +typedef enum { + /*! No laser connected. */ + LASER_NONE = 0, + + /*! 850nm Infra-Red VCSEL v1 */ + LASER_H_V1 = 1, + + /*! 850nm Infra-Red VCSEL v2 */ + LASER_H_V2 = 2, + + /*! 680nm Red VCSEL v1 */ + LASER_R_V1 = 3, + +} argus_laser_type_t; + +/*!*************************************************************************** + * @brief The AFBR-S50 chip versions. + *****************************************************************************/ +typedef enum { + /*! No device connected or not recognized. */ + ADS0032_NONE = 0, + + /*! ADS0032 v1.0 */ + ADS0032_V1_0 = 1, + + /*! ADS0032 v1.1 */ + ADS0032_V1_1 = 2, + + /*! ADS0032 v1.2 */ + ADS0032_V1_2 = 3, + +} 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. + *****************************************************************************/ +typedef enum { + /*! Measurement Mode A: Long Range Mode. */ + ARGUS_MODE_A = 1, + + /*! Measurement Mode B: Short Range Mode. */ + ARGUS_MODE_B = 2, + +} argus_mode_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). + *****************************************************************************/ +typedef status_t (*argus_callback_t)(status_t status, void *data); + +/*! @} */ +#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 new file mode 100644 index 0000000000..c5ec32dfa8 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_dfm.h @@ -0,0 +1,81 @@ +/*************************************************************************//** + * @file + * @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 + * 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_DFM_H +#define ARGUS_DFM_H + +/*!*************************************************************************** + * @defgroup argusdfm Dual Frequency Mode + * @ingroup argusdev + * + * @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. + * + * 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 + * @{ + *****************************************************************************/ + +/*! The Dual Frequency Mode frequency count. */ +#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! + +/*! The Dual Frequency Mode measurement modes enumeration. */ +typedef enum { + /*! Single Frequency Measurement Mode (w/ 1x Unambiguous Range). */ + DFM_MODE_OFF = 0U, + + /*! 4X Dual Frequency Measurement Mode (w/ 4x Unambiguous Range). */ + DFM_MODE_4X = 1U, + + /*! 8X Dual Frequency Measurement Mode (w/ 8x Unambiguous Range). */ + DFM_MODE_8X = 2U, + +} argus_dfm_mode_t; + + +/*! @} */ +#endif /* ARGUS_DFM_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 new file mode 100644 index 0000000000..e57de35711 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_meas.h @@ -0,0 +1,118 @@ +/*************************************************************************//** + * @file + * @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 + * 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_MEAS_H +#define ARGUS_MEAS_H + +/*!*************************************************************************** + * @defgroup argusmeas Measurement/Device Control + * @ingroup argusapi + * + * @brief Measurement/Device control module + * + * @details This module contains measurement and device control specific + * definitions and methods. + * + * @addtogroup argusmeas + * @{ + *****************************************************************************/ + +#include "argus_dca.h" +#include "argus_def.h" + +/*! Number of raw data values. */ +#define ARGUS_RAW_DATA_VALUES 132U // 33 channels * 4 phases + +/*! Size of the raw data in bytes. */ +#define ARGUS_RAW_DATA_SIZE (3U * ARGUS_RAW_DATA_VALUES) // 3 bytes * 33 channels * 4 phases + +/*! The number channels for auxiliary measurements readout. */ +#define ARGUS_AUX_CHANNEL_COUNT (5U) + +/*! Size of the auxiliary data in bytes. */ +#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. + *****************************************************************************/ +typedef struct { + /*! ADC channel enabled mask for the first + * channels 0 .. 31 (active pixels channels). + * See [pixel mapping](@ref argusmap) 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 + * details on the channel mask. */ + uint32_t ChEnMask; + + /*! Pattern count per sample in uq10.6 format. + * Determines the analog integration depth. */ + uq10_6_t AnalogIntegrationDepth; + + /*! Sample count per phase/frame. + * Determines the digital integration depth. */ + uint16_t DigitalIntegrationDepth; + + /*! Laser current per sample in mA. + * Determines the optical output power. */ + uq12_4_t OutputPower; + + /*! Charge pump voltage per sample in LSB. + * Determines the pixel gain. */ + uint8_t PixelGain; + + /*! PLL Frequency Offset, caused by temperature + * compensation, in PLL_INT_PRD LSBs. */ + int8_t PllOffset; + + /*! The current state of the measurement frame: + * - Measurement Mode, + * - A/B Frame, + * - PLL_Locked Bit, + * - BGL Warning/Error, + * - DCA State, + * - ... */ + argus_state_t State; + +} argus_meas_frame_t; + +/*! @} */ +#endif /* ARGUS_MEAS_H */ diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_msk.h b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_msk.h new file mode 100644 index 0000000000..258fb38260 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_msk.h @@ -0,0 +1,170 @@ +/*************************************************************************//** + * @file + * @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 + * 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_MSK_H +#define ARGUS_MSK_H + +/*!*************************************************************************** + * @defgroup argusmap ADC Channel Mapping + * @ingroup argusres + * + * @brief Pixel ADC Channel (n) to x-y-Index Mapping + * + * @details The ADC Channels of each pixel or auxiliary channel on the device + * is numbered in a way that is convenient on the chip. The macros + * in this module are defined in order to obtain the x-y-indices of + * each channel and vice versa. + * + * @addtogroup argusmap + * @{ + *****************************************************************************/ + +#include "api/argus_def.h" +#include "utility/int_math.h" + +/*!***************************************************************************** + * @brief Macro to determine the channel number of an specified Pixel. + * @param x The x index of the pixel. + * @param y The y index of the pixel. + * @return The channel number n of the pixel. + ******************************************************************************/ +#define PIXEL_XY2N(x, y) ((((x) ^ 7) << 1) | ((y) & 2) << 3 | ((y) & 1)) + +/*!***************************************************************************** + * @brief Macro to determine the x index of an specified Pixel channel. + * @param n The channel number of the pixel. + * @return The x index number of the pixel. + ******************************************************************************/ +#define PIXEL_N2X(n) ((((n) >> 1U) & 7) ^ 7) + +/*!***************************************************************************** + * @brief Macro to determine the y index of an specified Pixel channel. + * @param n The channel number of the pixel. + * @return The y index number of the pixel. + ******************************************************************************/ +#define PIXEL_N2Y(n) (((n) & 1U) | (((n) >> 3) & 2U)) + +/*!***************************************************************************** + * @brief Macro to determine if a ADC Pixel channel was enabled from a pixel mask. + * @param msk The 32-bit pixel mask + * @param ch The channel number of the pixel. + * @return True if the pixel channel n was enabled, false elsewise. + ******************************************************************************/ +#define PIXELN_ISENABLED(msk, ch) (((msk) >> (ch)) & 0x01U) + +/*!***************************************************************************** + * @brief Macro enables an ADC Pixel channel in a pixel mask. + * @param msk The 32-bit pixel mask + * @param ch The channel number of the pixel. + ******************************************************************************/ +#define PIXELN_ENABLE(msk, ch) ((msk) |= (0x01U << (ch))) + +/*!***************************************************************************** + * @brief Macro disables an ADC Pixel channel in a pixel mask. + * @param msk The 32-bit pixel mask + * @param ch The channel number of the pixel. + ******************************************************************************/ +#define PIXELN_DISABLE(msk, ch) ((msk) &= (~(0x01U << (ch)))) + +/*!***************************************************************************** + * @brief Macro to determine if an ADC Pixel channel was enabled from 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) was enabled, false elsewise. + ******************************************************************************/ +#define PIXELXY_ISENABLED(msk, x, y) (PIXELN_ISENABLED(msk, PIXEL_XY2N(x, y))) + +/*!***************************************************************************** + * @brief Macro enables an ADC Pixel channel in a pixel mask. + * @param msk 32-bit pixel mask + * @param x x index of the pixel. + * @param y y index of the pixel. + ******************************************************************************/ +#define PIXELXY_ENABLE(msk, x, y) (PIXELN_ENABLE(msk, PIXEL_XY2N(x, y))) + +/*!***************************************************************************** + * @brief Macro disables an ADC Pixel channel in a pixel mask. + * @param msk 32-bit pixel mask + * @param x x index of the pixel. + * @param y y index of the pixel. + ******************************************************************************/ +#define PIXELXY_DISABLE(msk, x, y) (PIXELN_DISABLE(msk, PIXEL_XY2N(x, y))) + +/*!***************************************************************************** + * @brief Macro to determine if a ADC channel was enabled from a channel mask. + * @param msk 32-bit channel mask + * @param ch channel number of the ADC channel. + * @return True if the ADC channel n was enabled, false elsewise. + ******************************************************************************/ +#define CHANNELN_ISENABLED(msk, ch) (((msk) >> ((ch) - 32U)) & 0x01U) + +/*!***************************************************************************** + * @brief Macro to determine if a ADC channel was enabled from a channel mask. + * @param msk 32-bit channel mask + * @param ch channel number of the ADC channel. + * @return True if the ADC channel n was enabled, false elsewise. + ******************************************************************************/ +#define CHANNELN_ENABLE(msk, ch) ((msk) |= (0x01U << ((ch) - 32U))) + +/*!***************************************************************************** + * @brief Macro to determine if a ADC channel was enabled from a channel mask. + * @param msk 32-bit channel mask + * @param ch channel number of the ADC channel. + * @return True if the ADC channel n was enabled, false elsewise. + ******************************************************************************/ +#define CHANNELN_DISABLE(msk, ch) ((msk) &= (~(0x01U << ((ch) - 32U)))) + + +/*!***************************************************************************** + * @brief Macro to determine the number of enabled pixel channels 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. + ******************************************************************************/ +#define CHANNEL_COUNT(pxmsk, chmsk) (popcount(pxmsk) + popcount(chmsk)) + +/*! @} */ +#endif /* ARGUS_MSK_H */ 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 new file mode 100644 index 0000000000..1d4a5c43b9 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_pba.h @@ -0,0 +1,221 @@ +/*************************************************************************//** + * @file + * @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 + * 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_PBA_H +#define ARGUS_PBA_H + +/*!*************************************************************************** + * @defgroup arguspba Pixel Binning Algorithm + * @ingroup argusapi + * + * @brief Pixel Binning Algorithm (PBA) parameter definitions and API functions. + * + * @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 pixel filter 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. + * . + * + * 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 + * @{ + *****************************************************************************/ + +#include "argus_def.h" + +/*!*************************************************************************** + * @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 + * . + *****************************************************************************/ +typedef enum { + /*! Enables the pixel binning feature. */ + PBA_ENABLE = 1U << 0U, + + /*! Enables the golden pixel. */ + PBA_ENABLE_GOLDPX = 1U << 5U, + + /*! Enables the minimum distance scope filter. */ + PBA_ENABLE_MIN_DIST_SCOPE = 1U << 6U, + +} argus_pba_flags_t; + +/*!*************************************************************************** + * @brief The averaging modes for the pixel binning algorithm. + *****************************************************************************/ +typedef enum { + /*! Evaluate the 1D range from all available pixels using + * a simple average. */ + PBA_SIMPLE_AVG = 1U, + + /*! Evaluate the 1D range from all available pixels using + * a linear amplitude weighted averaging method. + * Formula: x_mean = sum(x_i * A_i) / sum(A_i) */ + PBA_LINEAR_AMPLITUDE_WEIGHTED_AVG = 2U, + +} argus_pba_averaging_mode_t; + +/*!*************************************************************************** + * @brief The pixel binning algorithm settings data structure. + * @details Describes the pixel binning algorithm settings. + *****************************************************************************/ +typedef struct { + /*! Enables the pixel binning features. + * Each bit may enable a different feature. See #argus_pba_flags_t + * for details about the enabled flags. */ + argus_pba_flags_t Enabled; + + /*! Determines the PBA averaging mode which is used to obtain the + * final range value from the algorithm, for example, the average + * of all pixels. See #argus_pba_averaging_mode_t for more details + * about the individual evaluation modes. */ + argus_pba_averaging_mode_t Mode; + + /*! 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. + * The actual percentage value is determined by 100%/256*x. + * + * Use 0 to disable the relative amplitude threshold. */ + 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. + * + * All available values from the 8-bit representation are valid. + * The actual percentage value is determined by 100%/256*x. + * + * 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)! */ + uq0_8_t RelMinDistanceScope; + + /*! The Absolute amplitude threshold value in LSB. + * Pixels with amplitude below this threshold value are dismissed. + * + * All available values from the 16-bit representation are valid. + * The actual LSB value is determined by x/16. + * + * Use 0 to disable the absolute amplitude threshold. */ + 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. + * + * All available values from the 16-bit representation are valid. + * The actual LSB value is determined by x/2^15. + * + * 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)! */ + 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 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; + +/*! @} */ +#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 new file mode 100644 index 0000000000..2977312d8e --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_px.h @@ -0,0 +1,143 @@ +/*************************************************************************//** + * @file + * @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 + * 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_PX_H +#define ARGUS_PX_H + +/*!*************************************************************************** + * @addtogroup argusres + * @{ + *****************************************************************************/ + +/*! Maximum amplitude value in UQ12.4 format. */ +#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. + * + * @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. + * . + *****************************************************************************/ +typedef enum { + /*! 0x00: Pixel status OK. */ + PIXEL_OK = 0, + + /*! 0x01: Pixel is disabled (in hardware) and no data has been read from the device. */ + PIXEL_OFF = 1U << 0U, + + /*! 0x02: Pixel is saturated (i.e. at least one saturation bit for any + * sample is set or the sample is in the invalidity area). */ + PIXEL_SAT = 1U << 1U, + + /*! 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, + + /*! 0x10: Pixel is pre-filtered by the static pixel binning pre-filter mask, + * 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). */ + 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. */ + 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_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. + *****************************************************************************/ +typedef struct { + /*! Range Values from the device in meter. It is the actual distance before + * software adjustments/calibrations. */ + q9_22_t Range; + + /*! Phase Values from the device in units of PI, i.e. 0 ... 2. */ + uq1_15_t Phase; + + /*! Amplitudes of measured signals in LSB. + * Special values: 0 == Pixel Off, 0xFFFF == Overflow/Error */ + uq12_4_t Amplitude; + + /*! Pixel status; determines if the pixel is disabled, saturated, .. + * See the \link #argus_px_status_t pixel status flags\endlink for more + * information. */ + argus_px_status_t Status; + + /*! The unambiguous window determined by the dual frequency feature. */ + int8_t RangeWindow; + + /*! The raw amplitudes of measured signals in LSB. */ + uq12_4_t AmplitudeRaw; + +} argus_pixel_t; + + +/*! @} */ +#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 new file mode 100644 index 0000000000..468a913671 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_res.h @@ -0,0 +1,173 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 API. + * @details Defines the generic measurement results data structure. + * + * @copyright + * + * Copyright (c) 2021, 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_RES_H +#define ARGUS_RES_H + +/*!*************************************************************************** + * @defgroup argusres Measurement Data + * @ingroup argusapi + * + * @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 + * - ... + * . + * + * @addtogroup argusres + * @{ + *****************************************************************************/ + +#include "argus_def.h" +#include "argus_px.h" +#include "argus_meas.h" + +/*!*************************************************************************** + * @brief The 1d measurement results data structure. + * @details The 1d measurement results obtained by the pixel binning algorithm. + *****************************************************************************/ +typedef struct { + /*! 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; + + /*! The 1D amplitude in LSB (Q12.4 format). The (maximum) amplitude obtained + * by the pixel binning algorithm from the current measurement frame. + * Special value: 0 == No/Invalid Result. */ + uq12_4_t Amplitude; + +} argus_results_bin_t; + +/*!*************************************************************************** + * @brief The auxiliary measurement results data structure. + * @details The auxiliary measurement results obtained by the auxiliary task. + * Special values, i.e. 0xFFFFU, indicate no readout value available. + *****************************************************************************/ +typedef struct { + /*! VDD ADC channel readout value. + * Special Value if no value has been measured: + * Invalid/NotAvailable = 0xFFFFU (UQ12_4_MAX) */ + uq12_4_t VDD; + + /*! Temperature sensor ADC channel readout value. + * Special Value if no value has been measured: + * Invalid/NotAvailable = 0x7FFFU (Q11_4_MAX) */ + q11_4_t TEMP; + + /*! Substrate Voltage ADC Channel readout value. + * Special Value if no value has been measured: + * Invalid/NotAvailable = 0xFFFFU (UQ12_4_MAX) */ + uq12_4_t VSUB; + + /*! VDD VCSEL ADC channel readout value. + * Special Value if no value has been measured: + * Invalid/NotAvailable = 0xFFFFU (UQ12_4_MAX) */ + uq12_4_t VDDL; + + /*! APD current ADC Channel readout value. + * Special Value if no value has been measured: + * Invalid/NotAvailable = 0xFFFFU (UQ12_4_MAX) */ + uq12_4_t IAPD; + + /*! Background Light Value in arbitrary. units, + * estimated by the substrate voltage control task. + * Special Value if no value is available: + * Invalid/NotAvailable = 0xFFFFU (UQ12_4_MAX) */ + uq12_4_t BGL; + + /*! Shot Noise Amplitude in LSB units, + * estimated by the shot noise monitor task from + * the average amplitude of the passive pixels. + * Special Value if no value is available: + * Invalid/NotAvailable = 0xFFFFU (UQ12_4_MAX) */ + uq12_4_t SNA; + +} argus_results_aux_t; + +/*!*************************************************************************** + * @brief The measurement results data structure. + * @details Measurement data from the device. + * @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 + * @endcode + *****************************************************************************/ +typedef struct { + /*! 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. + * - < 0 for errors and invalid measurement signal. */ + status_t Status; + + /*! Time in milliseconds (measured since the last MCU startup/reset) + * when the measurement was triggered. */ + ltc_t TimeStamp; + + /*! The configuration for the current measurement frame. */ + argus_meas_frame_t Frame; + + /*! Raw unmapped ADC results from the device. */ + uint32_t Data[ARGUS_RAW_DATA_VALUES]; + + /*! Raw Range Values from the device in meter. + * It is the actual distance before software adjustments/calibrations. */ + argus_pixel_t PixelRef; + + /*! Raw Range Values from the device in meter. + * It is the actual distance before software adjustments/calibrations. */ + argus_pixel_t Pixel[ARGUS_PIXELS_X][ARGUS_PIXELS_Y]; + + /*! Pixel binned results. */ + argus_results_bin_t Bin; + + /*! The auxiliary ADC channel data. */ + argus_results_aux_t Auxiliary; + +} argus_results_t; + + +/*! @} */ +#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 new file mode 100644 index 0000000000..2bb6ad0a0d --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_snm.h @@ -0,0 +1,82 @@ +/*************************************************************************//** + * @file + * @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 + * 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_SNM_H +#define ARGUS_SNM_H + +/*!*************************************************************************** + * @defgroup argussnm Shot Noise Monitor + * @ingroup argusdev + * + * @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. + * + * 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 + * @{ + *****************************************************************************/ + +/*! The Shot Noise Monitor modes enumeration. */ +typedef enum { + /*! 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. + * Equivalent to Shot Noise Monitoring disabled. */ + SNM_MODE_STATIC_INDOOR = 0U, + + /*! Static Shot Noise Monitoring Mode, optimized for outdoor applications. + * Assumes the worst case scenario, i.e. it uses a fixed setting that will + * work under all ambient conditions. */ + SNM_MODE_STATIC_OUTDOOR = 1U, + + /*! Dynamic Shot Noise Monitoring Mode. + * Adopts the system performance dynamically to the current ambient conditions. */ + SNM_MODE_DYNAMIC = 2U, + +} argus_snm_mode_t; + + +/*! @} */ +#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 new file mode 100644 index 0000000000..77ac2fcafc --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_status.h @@ -0,0 +1,271 @@ +/*************************************************************************//** + * @file + * @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 + * 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_STATUS_H +#define ARGUS_STATUS_H + +#include + +/*!*************************************************************************** + * @defgroup status Status Codes + * @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 + * @{ + *****************************************************************************/ + +/*!*************************************************************************** + * @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. + * . + *****************************************************************************/ +typedef int32_t status_t; + +/*! AFBR-S50 API status and error return codes. */ +enum Status { + /********************************************************************************************** + ********** Generic Status ******************************************************************** + *********************************************************************************************/ + + /*! 0: Status for success/no error. */ + STATUS_OK = 0, + + /*! 0: Status for device/module/hardware idle. Implies #STATUS_OK. */ + STATUS_IDLE = 0, + + /*! 1: Status to be ignored. */ + STATUS_IGNORE = 1, + + /*! 2: Status for device/module/hardware busy. */ + STATUS_BUSY = 2, + + /*! 3: Status for device/module/hardware is currently initializing. */ + STATUS_INITIALIZING = 3, + + /*! -1: Error for generic fail/error. */ + ERROR_FAIL = -1, + + /*! -2: Error for process aborted by user/external. */ + ERROR_ABORTED = -2, + + /*! -3: Error for invalid read only operations. */ + ERROR_READ_ONLY = -3, + + /*! -4: Error for out of range parameters. */ + ERROR_OUT_OF_RANGE = -4, + + /*! -5: Error for invalid argument passed to an function. */ + ERROR_INVALID_ARGUMENT = -5, + + /*! -6: Error for timeout occurred. */ + ERROR_TIMEOUT = -6, + + /*! -7: Error for not initialized modules. */ + ERROR_NOT_INITIALIZED = -7, + + /*! -8: Error for not supported. */ + ERROR_NOT_SUPPORTED = -8, + + /*! -9: Error for yet not implemented functions. */ + ERROR_NOT_IMPLEMENTED = -9, + + + /********************************************************************************************** + ********** S2PI Layer Status ***************************************************************** + *********************************************************************************************/ + + /*! 51: SPI is disabled and pins are used in GPIO mode. */ + STATUS_S2PI_GPIO_MODE = 51, + + /*! -51: Error occurred on the Rx line. */ + ERROR_S2PI_RX_ERROR = -51, + + /*! -52: Error occurred on the Tx line. */ + ERROR_S2PI_TX_ERROR = -52, + + /*! -53: Called a function at a wrong driver state. */ + ERROR_S2PI_INVALID_STATE = -53, + + /*! -54: The specified baud rate is not valid. */ + ERROR_S2PI_INVALID_BAUD_RATE = -54, + + /*! -55: The specified slave identifier is not valid. */ + ERROR_S2PI_INVALID_SLAVE = -55, + + + /********************************************************************************************** + ********** 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, + + /*! -99: Flash Error: The memory is out of range. */ + ERROR_NVM_OUT_OF_RANGE = -99, + + + /********************************************************************************************** + ********** AFBR-S50 Specific Status ********************************************************** + *********************************************************************************************/ + + /*! 104: AFBR-S50 Status: All (internal) raw data buffers are currently in use. + * The measurement was not executed due to lack of available raw data buffers. + * Please call #Argus_EvaluateData to free the buffers. */ + STATUS_ARGUS_BUFFER_BUSY = 104, + + /*! 105: AFBR-S50 Status: The measurement was not executed/started due to output power + * limitations. */ + STATUS_ARGUS_POWERLIMIT = 105, + + /*! 107: AFBR-S50 Status: No valid signal was detected at any active pixel + * via the Pixel Binning Algorithm. The Golden Pixel was Choosen as a + * fallback value that is consider to be the last pixel that has a valid + * signal for low reflective (or far away) objects. + * The current results should be considered carefully. */ + STATUS_ARGUS_UNDERFLOW = 107, + + /*! 108: AFBR-S50 Status: No object was detected within the field-of-view + * and measurement range of the device. */ + STATUS_ARGUS_NO_OBJECT = 108, + + /*! 109: AFBR-S50 Status: The readout algorithm for the EEPROM has detected a bit + * error which has been corrected. However, if more than a single bit error + * has occurred, the corrected value is invalid! This cannot be distinguished + * from the valid case. Thus, if the error starts to occur, the sensor + * should be replaced soon! */ + STATUS_ARGUS_EEPROM_BIT_ERROR = 109, + + /*! 110: AFBR-S50 Status: Inconsistent EEPROM readout data. No calibration + * trimming values are applied. The calibration remains invalid. */ + STATUS_ARGUS_INVALID_EEPROM = 110, + + /*! -101: AFBR-S50 Error: No device connected. Initial SPI tests failed. */ + ERROR_ARGUS_NOT_CONNECTED = -101, + + /*! -102: AFBR-S50 Error: Inconsistent configuration parameters. */ + ERROR_ARGUS_INVALID_CFG = -102, + + + /*! -105: AFBR-S50 Error: Invalid measurement mode configuration parameter. */ + ERROR_ARGUS_INVALID_MODE = -105, + + /*! -107: AFBR-S50 Error: The APD bias voltage is reinitializing due to a dropout. + * 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 + * such that it is usually temporarily and due to harsh ambient conditions. */ + ERROR_ARGUS_EEPROM_FAILURE = -109, + + /*! -110: AFBR-S50 Error: The measurement signals of all active pixels are invalid + * and thus the 1D range is also invalid and stalled. + * This means the range value is not updated and kept at the previous valid value. */ + ERROR_ARGUS_STALLED = -110, + + /*! -111: AFBR-S50 Error: The background light is too bright. */ + ERROR_ARGUS_BGL_EXCEEDANCE = -111, + + /*! -112: AFBR-S50 Error: The crosstalk vector amplitude is too high. */ + ERROR_ARGUS_XTALK_AMPLITUDE_EXCEEDANCE = -112, + + /*! -113: AFBR-S50 Error: Laser malfunction! Laser Safety may not be given! */ + ERROR_ARGUS_LASER_FAILURE = -113, + + /*! -114: AFBR-S50 Error: Register data integrity is lost (e.g. due to unexpected + * power-on-reset cycle or invalid write cycle of SPI. System tries to + * reset the values. */ + ERROR_ARGUS_DATA_INTEGRITY_LOST = -114, + + /*! -115: AFBR-S50 Error: The range offsets calibration failed! */ + ERROR_ARGUS_RANGE_OFFSET_CALIBRATION_FAILED = -115, + + /*! -191: AFBR-S50 Error: The device is currently busy and cannot execute the + * requested command. */ + ERROR_ARGUS_BUSY = -191, + + + /*! -199: AFBR-S50 Error: Unknown module number. */ + ERROR_ARGUS_UNKNOWN_MODULE = -199, + + /*! -198: AFBR-S50 Error: Unknown chip version number. */ + ERROR_ARGUS_UNKNOWN_CHIP = -198, + + /*! -197: AFBR-S50 Error: Unknown laser type number. */ + ERROR_ARGUS_UNKNOWN_LASER = -197, + + + + /*! 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, + + /*! 195: AFBR-S50 Status (internal): The device is currently executing a calibration + * 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, + + /*! 103: AFBR-S50 Status (internal): The ASIC is performing an integration cycle. */ + STATUS_ARGUS_ACTIVE = 103, + + + +}; + +/*! @} */ +#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 new file mode 100644 index 0000000000..2a2ca9d62c --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_version.h @@ -0,0 +1,76 @@ +/*************************************************************************//** + * @file + * @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 + * 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_VERSION_H +#define ARGUS_VERSION_H + +/*!*************************************************************************** + * @defgroup version API Version + * @ingroup argusapi + * + * @brief API and library core version number + * + * @details Contains the AFBR-S50 API and Library Core Version Number. + * + * @addtogroup version + * @{ + *****************************************************************************/ + +/*! Major version number of the AFBR-S50 API. */ +#define ARGUS_API_VERSION_MAJOR 1 + +/*! Minor version number of the AFBR-S50 API. */ +#define ARGUS_API_VERSION_MINOR 2 + +/*! Bugfix version number of the AFBR-S50 API. */ +#define ARGUS_API_VERSION_BUGFIX 3 + +/*! Build version nunber of the AFBR-S50 API. */ +#define ARGUS_API_VERSION_BUILD "20201120091253" + +/*****************************************************************************/ + +/*! Construct the version number for drivers. */ +#define MAKE_VERSION(major, minor, bugfix) \ + (((major) << 24) | ((minor) << 16) | (bugfix)) + +/*! Version number of the AFBR-S50 API. */ +#define ARGUS_API_VERSION MAKE_VERSION((ARGUS_API_VERSION_MAJOR), \ + (ARGUS_API_VERSION_MINOR), \ + (ARGUS_API_VERSION_BUGFIX)) + +/*! @} */ +#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 new file mode 100644 index 0000000000..5613706267 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/api/argus_xtalk.h @@ -0,0 +1,114 @@ +/*************************************************************************//** + * @file + * @brief This file is part of the AFBR-S50 hardware API. + * @details Defines the generic device calibration API. + * + * @copyright + * + * Copyright (c) 2021, 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_XTALK_H +#define ARGUS_XTALK_H + +/*!*************************************************************************** + * @addtogroup arguscal + * @{ + *****************************************************************************/ + +#include "api/argus_def.h" + +/*!*************************************************************************** + * @brief Pixel Crosstalk Compensation Vector. + * @details Contains calibration data (per pixel) that belongs to the + * RX-TX-Crosstalk compensation feature. + *****************************************************************************/ + +/*! Pixel Crosstalk Vector */ +typedef struct { + /*! Crosstalk Vector - Sine component. + * Special Value: Q11_4_MIN == not available */ + q11_4_t dS; + + /*! Crosstalk Vector - Cosine component. + * 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. + *****************************************************************************/ +typedef struct { + /*! 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 + * ratio is smaller than the value, the compensation is active. Absolute + * and relative conditions are connected with AND logic. */ + uq0_8_t RelativeThreshold; + + /*! The absolute threshold determines the minimum total crosstalk + * amplitude (i.e. the average amplitude of all pixels weighted by + * the Kc factor) that is required for the compensation to become + * active. Set to 0 to always enable. Absolute and relative + * conditions are connected with AND logic. */ + uq12_4_t AbsoluteTreshold; + + /*! The sine component of the Kc factor that determines the amount of the total + * signal of all pixels that influences the individual signal of each pixel. + * Higher values determine more influence on the individual pixel signal. */ + q3_12_t KcFactorS; + + /*! The cosine component of the Kc factor that determines the amount of the total + * signal of all pixels that influences the individual signal of each pixel. + * Higher values determine more influence on the individual pixel signal. */ + q3_12_t KcFactorC; + + /*! The sine component of the reference pixel Kc factor that determines the + * amount of the total signal on all pixels that influences the individual + * signal of the reference pixel. + * Higher values determine more influence on the reference pixel signal. */ + q3_12_t KcFactorSRefPx; + + /*! The cosine component of the reference pixel Kc factor that determines the + * amount of the total signal on all pixels that influences the individual + * signal of the reference pixel. + * Higher values determine more influence on the reference pixel signal. */ + q3_12_t KcFactorCRefPx; + +} argus_cal_p2pxtalk_t; + + +/*! @} */ +#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 new file mode 100644 index 0000000000..dcea881d02 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/argus.h @@ -0,0 +1,50 @@ +/*************************************************************************//** + * @file + * @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 + * 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_H +#define ARGUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "api/argus_api.h" + +#ifdef __cplusplus +} +#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 new file mode 100644 index 0000000000..0a9e11241e --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_irq.h @@ -0,0 +1,121 @@ +/*************************************************************************//** + * @file + * @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 + * 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_IRQ_H +#define ARGUS_IRQ_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @defgroup argus_irq IRQ: Global Interrupt Control Layer + * @ingroup argus_platform + * + * @brief Global Interrupt Control Layer + * + * @details This module provides functionality to globally enable/disable + * interrupts by turning the I-bit in the CPSR on/off. + * + * 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 + * + * // 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 lock all interrupts using CMSIS function "__disable_irq()". + * void IRQ_LOCK(void) + * { + * __disable_irq(); + * g_irq_lock_ct++; + * } + * + * @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 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 + * @{ + *****************************************************************************/ + +/*!*************************************************************************** + * @brief Enable IRQ Interrupts + * + * @details Enables IRQ interrupts by clearing the I-bit in the CPSR. + * Can only be executed in Privileged modes. + *****************************************************************************/ +void IRQ_UNLOCK(void); + +/*!*************************************************************************** + * @brief Disable IRQ Interrupts + * + * @details Disables IRQ interrupts by setting the I-bit in the CPSR. + * Can only be executed in Privileged modes. + *****************************************************************************/ +void IRQ_LOCK(void); + +#ifdef __cplusplus +} +#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 new file mode 100644 index 0000000000..0c0bf237cb --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_nvm.h @@ -0,0 +1,135 @@ +/*************************************************************************//** + * @file + * @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 + * 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_NVM_H +#define ARGUS_NVM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @defgroup argus_nvm NVM: Non-Volatile Memory Layer + * @ingroup argus_platform + * + * @brief Non-Volatile Memory Layer + * + * @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. + * + * @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 + * @{ + *****************************************************************************/ + +#include "argus.h" + +/*!*************************************************************************** + * @brief Initializes the non-volatile memory unit and reserves a chunk of 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. + * + * If not implemented, the function should return #ERROR_NOT_IMPLEMENTED + * in oder to inform the API to not use the NVM module. + * + * After initialization, the API calls the #NVM_Write and #NVM_Read + * methods to write within the reserved chunk of memory. + * + * @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 size The required size of NVM to store all parameters. + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t NVM_Init(uint32_t size); + +/*!*************************************************************************** + * @brief Write a block of data to 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. + * + * 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 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). + *****************************************************************************/ +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 + +/*! @} */ +#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 new file mode 100644 index 0000000000..d53b2df35a --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_print.h @@ -0,0 +1,83 @@ +/*************************************************************************//** + * @file + * @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 + * 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_PRINT_H +#define ARGUS_PRINT_H + +/*!*************************************************************************** + * @defgroup argus_log Debug: Logging Interface + * @ingroup argus_platform + * + * @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. + * + * @addtogroup argus_log + * @{ + *****************************************************************************/ + +#include "api/argus_def.h" + +/*!*************************************************************************** + * @brief A printf-like function to print formated 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. + * + * 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 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). + *****************************************************************************/ +status_t print(const char *fmt_s, ...); + +/*! @} */ +#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 new file mode 100644 index 0000000000..3c17096e8e --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_s2pi.h @@ -0,0 +1,352 @@ +/*************************************************************************//** + * @file + * @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 + * 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_S2PI_H +#define ARGUS_S2PI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @defgroup argus_s2pi S2PI: Serial Peripheral Interface + * @ingroup argus_platform + * + * @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. + * + * **SPI interface:** + * + * The SPI interface is based on a single function: + * + * #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. + * + * **GPIO interface:** + * + * 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 the + * pending. 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 the interface. One basic operation would + * be to cycle the chip select pin which resets the device. + * Additional, the device contains an EEPROM that 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 + * 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. + * + * Note that the GPIO mode is only required to readout the EEPROM + * at initialization of the device, i.e. during execution of the + * #Argus_Init or #Argus_Reinit method. The GPIO mode is not used + * during measurements. + * + * + * @addtogroup argus_s2pi + * @{ + *****************************************************************************/ + +#include "api/argus_def.h" + +/*!*************************************************************************** + * @brief S2PI layer callback function type for the SPI transfer completed event. + * + * @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. + * + * @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. + * + * @param param The provided (optional, can be null) callback parameter. + *****************************************************************************/ +typedef void (*s2pi_irq_callback_t)(void *param); + +/*! The S2PI slave identifier. Basically an user defined enumerable type that + * 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 SPI clock pin. */ + S2PI_CLK, + + /*! The SPI chip select pin. */ + S2PI_CS, + + /*! The SPI MOSI pin. */ + S2PI_MOSI, + + /*! The SPI MISO pin. */ + S2PI_MISO, + + /*! The IRQ pin. */ + S2PI_IRQ + +} s2pi_pin_t; + + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +status_t S2PI_GetStatus(void); + +/*!*************************************************************************** + * @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. + * + * 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. + * + * 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. + * + * 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 + * (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 + * 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. + *****************************************************************************/ +status_t S2PI_TransferFrame(s2pi_slave_t slave, + uint8_t const *txData, + uint8_t *rxData, + size_t frameSize, + s2pi_callback_t callback, + void *callbackData); + +/*!*************************************************************************** + * @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. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t S2PI_Abort(void); + +/*!*************************************************************************** + * @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 + * 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. + *****************************************************************************/ +status_t S2PI_SetIrqCallback(s2pi_slave_t slave, s2pi_irq_callback_t callback, void *callbackData); + +/*!*************************************************************************** + * @brief Reads the current 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. + * + * 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! + + * @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). + *****************************************************************************/ +uint32_t S2PI_ReadIrqPin(s2pi_slave_t slave); + +/*!*************************************************************************** + * @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. + * + * @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. + * + * @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! + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t S2PI_CaptureGpioControl(void); + +/*!***************************************************************************** + * @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. + * + * @note This function is only called during device initialization! + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t S2PI_ReleaseGpioControl(void); + +/*!***************************************************************************** + * @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. + * + * @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! + * + * @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. + * + * @details This function reads the value of an SPI pin if the SPI pins are + * captured for GPIO operation via the #S2PI_CaptureGpioControl previously. + * + * @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, 1 = high). + * @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 + +/*! @} */ +#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 new file mode 100644 index 0000000000..0390020aff --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/platform/argus_timer.h @@ -0,0 +1,267 @@ +/*************************************************************************//** + * @file + * @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 + * 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_TIMER_H +#define ARGUS_TIMER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*!*************************************************************************** + * @defgroup argus_timer Timer: Hardware Timer Interface + * @ingroup argus_platform + * + * @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. + * + * 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 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 + * or #Timer_Start. From then, the callback is invoked periodically as + * the corresponding interval may specify. The timer is stopped via + * #Timer_Stop or by setting the interval to 0. 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_Start method twice, but + * with an individual parameter pointer, ptr1 and ptr2, each: + * \code + * Timer_Start(100000, ptr1); // 10 ms callback w/ parameter ptr1 + * Timer_Start(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. + * + * @addtogroup argus_timer + * @{ + *****************************************************************************/ + +#include "api/argus_def.h" + +/******************************************************************************* + * Lifetime Counter Timer Interface + ******************************************************************************/ + +/*!*************************************************************************** + * @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: + * + * 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). + * + * 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. + * + * 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. + * + * @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 + *****************************************************************************/ +void Timer_GetCounterValue(uint32_t *hct, uint32_t *lct); + +/******************************************************************************* + * Periodic Interrupt Timer Interface + ******************************************************************************/ + +/*!*************************************************************************** + * @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. + * + * @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. + * + * @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. + * + * @param f The timer callback function. + * + * @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. + * + * @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. + * + * @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 dt_microseconds The callback interval in microseconds. + * + * @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); + +/*!*************************************************************************** + * @brief Starts the timer 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. + * 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. + * + * @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 dt_microseconds The callback interval in microseconds. + * + * @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_Start(uint32_t dt_microseconds, void *param); + +/*!*************************************************************************** + * @brief Stops the timer for a specified callback parameter. + * + * @details Stops a callback interval for the specified parameter. + * + * @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 that identifies the interval to be stopped. + * + * @return Returns the \link #status_t status\endlink (#STATUS_OK on success). + *****************************************************************************/ +status_t Timer_Stop(void *param); + +#ifdef __cplusplus +} +#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 new file mode 100644 index 0000000000..06f39681e5 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/fp_def.h @@ -0,0 +1,407 @@ +/*************************************************************************//** + * @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) 2021, 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_DEF_H +#define FP_DEF_H + +/*!*************************************************************************** + * @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. + * + * - 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 + * @{ + *****************************************************************************/ + +#include + +/******************************************************************************* + * 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 + *****************************************************************************/ +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 + *****************************************************************************/ +typedef uint8_t uq4_4_t; + +/*! Maximum value of UQ4.4 number format. */ +#define UQ4_4_MAX ((uq4_4_t)UINT8_MAX) + +/*! The 1/one/unity in UQ4.4 number format. */ +#define UQ4_4_ONE ((uq4_4_t)(1U<<4U)) + + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint8_t uq2_6_t; + +/*! The 1/one/unity in UQ2.6 number format. */ +#define UQ2_6_ONE ((uq2_6_t)(1U<<6U)) + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint8_t uq1_7_t; + +/******************************************************************************* + * Q0.7 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q0.7 + * @details An signed fixed point number format based on the 8-bit integer + * type with 0 integer and 7 fractional bits. + * - Range: -1 .. 0.9921875 + * - Granularity: 0.0078125 + *****************************************************************************/ +//typedef int8_t q0_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 + *****************************************************************************/ +typedef uint8_t uq0_8_t; + +/*! Maximum value of UQ0.8 number format. */ +#define UQ0_8_MAX ((uq0_8_t)UINT8_MAX) + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint16_t uq12_4_t; + +/*! Maximum value of UQ12.4 number format. */ +#define UQ12_4_MAX ((uq12_4_t)UINT16_MAX) + +/*! The 1/one/unity in UQ12.4 number format. */ +#define UQ12_4_ONE ((uq12_4_t)(1U<<4U)) + +/******************************************************************************* + * Q11.4 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q11.4 + * @details An 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; + +/*! The 1/one/unity in UQ11.4 number format. */ +#define UQ11_4_ONE ((q11_4_t)(1 << 4)) + +/*! Maximum value of Q11.4 number format. */ +#define Q11_4_MAX ((q11_4_t)INT16_MAX) + +/*! Minimum value of Q11.4 number format. */ +#define Q11_4_MIN ((q11_4_t)INT16_MIN) + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint16_t uq10_6_t; + +/*! Maximum value of UQ10.6 number format. */ +#define UQ10_6_MAX ((uq10_6_t)UINT16_MAX) + +/*! The 1/one/unity in UQ10.6 number format. */ +#define UQ10_6_ONE ((uq10_6_t)(1U << 6U)) + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint16_t uq1_15_t; + +/*! Maximum value of UQ1.15 number format. */ +#define UQ1_15_MAX ((uq1_15_t)UINT16_MAX) + +/*! The 1/one/unity in UQ1.15 number format. */ +#define UQ1_15_ONE ((uq1_15_t)(1U << 15U)) + +/******************************************************************************* + * Q0.15 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q0.15 + * @details An 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; + +/******************************************************************************* + * Q2.13 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q2.13 + * @details An signed fixed point number format based on the 16-bit integer + * type with 2 integer and 13 fractional bits. + * - Range: -4 .. 3.99987793 + * - Granularity: 0.00012207 + *****************************************************************************/ +//typedef int16_t q2_13_t; + +/******************************************************************************* + * Q13.2 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q13.2 + * @details An signed fixed point number format based on the 16-bit integer + * type with 13 integer and 2 fractional bits. + * - Range: -8192 .. 8191.75 + * - Granularity: 0.25 + *****************************************************************************/ +//typedef int16_t q13_2_t; + + +/******************************************************************************* + * Q3.12 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q3.12 + * @details An 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; + + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint16_t uq0_16_t; + +/*! Maximum value of UQ0.16 number format. */ +#define UQ0_16_MAX ((uq0_16_t)UINT16_MAX) + +/*! The 1/one/unity in UQ0.16 number format. */ +#define UQ0_16_ONE ((uq0_16_t)(1U<<16U)) + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint32_t uq28_4_t; + +/*! Maximum value of UQ28.4 number format. */ +#define UQ28_4_MAX ((uq28_4_t)UINT32_MAX) + +/*! The 1/one/unity in UQ28.4 number format. */ +#define UQ28_4_ONE ((uq28_4_t)(1U<<4U)) + +/******************************************************************************* + * Q27.4 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q27.4 + * @details An 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; + +/*! The 1/one/unity in UQ27.4 number format. */ +#define UQ27_4_ONE ((q27_4_t)(1 << 4)) + +/*! Maximum value of Q27.4 number format. */ +#define Q27_4_MAX ((q27_4_t)INT32_MAX) + +/*! Minimum value of Q27.4 number format. */ +#define Q27_4_MIN ((q27_4_t)INT32_MIN) + + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint32_t uq16_16_t; + +/*! The 1/one/unity in UQ16.16 number format. */ +#define UQ16_16_ONE ((uq16_16_t)(1U << 16U)) + +/*! Maximum value of UQ16.16 number format. */ +#define UQ16_16_MAX ((uq16_16_t)UINT32_MAX) + +/*! Euler's number, e, in UQ16.16 format. */ +#define UQ16_16_E (0x2B7E1U) + + +/******************************************************************************* + * Q15.16 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q15.16 + * @details An 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; + +/*! The 1/one/unity in Q15.16 number format. */ +#define Q15_16_ONE ((q15_16_t)(1 << 16)) + +/*! Maximum value of Q15.16 number format. */ +#define Q15_16_MAX ((q15_16_t)INT32_MAX) + +/*! Minimum value of Q15.16 number format. */ +#define Q15_16_MIN ((q15_16_t)INT32_MIN) + +/******************************************************************************* + * 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 + *****************************************************************************/ +typedef uint32_t uq10_22_t; + +/******************************************************************************* + * Q9.22 + ******************************************************************************/ +/*!*************************************************************************** + * @brief Signed fixed point number: Q9.22 + * @details An 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; + +/*! The 1/one/unity in Q9.22 number format. */ +#define Q9_22_ONE ((q9_22_t)(1 << 22)) + +/*! Maximum value of Q9.22 number format. */ +#define Q9_22_MAX ((q9_22_t)INT32_MAX) + +/*! Minimum value of Q9.22 number format. */ +#define Q9_22_MIN ((q9_22_t)INT32_MIN) + +/*! @} */ +#endif /* FP_DEF_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 new file mode 100644 index 0000000000..ed96f7337a --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/Inc/utility/time.h @@ -0,0 +1,290 @@ +/*************************************************************************//** + * @file + * @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 + * 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 TIME_H +#define TIME_H + +/*!*************************************************************************** + * @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 + * @{ + *****************************************************************************/ + +#include +#include + +/*!*************************************************************************** + * @brief A data structure to represent current time. + * + * @details Value is obtained from the PIT time which must be configured as + * lifetime counter. + *****************************************************************************/ +typedef struct { + /*! Seconds. */ + uint32_t sec; + + /*! Microseconds. */ + uint32_t usec; + +} ltc_t; + +/*!*************************************************************************** + * @brief Obtains the elapsed time since MCU startup. + * @param t_now returned current time + *****************************************************************************/ +void Time_GetNow(ltc_t *t_now); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +uint32_t Time_GetNowUSec(void); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +uint32_t Time_GetNowMSec(void); + +/*!*************************************************************************** + * @brief Obtains the elapsed seconds since MCU startup. + * @param - + * @return Elapsed seconds since MCU startup as uint32_t. + *****************************************************************************/ +uint32_t Time_GetNowSec(void); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +void Time_GetElapsed(ltc_t *t_elapsed, ltc_t *const t_start); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +uint32_t Time_GetElapsedUSec(ltc_t *const t_start); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +uint32_t Time_GetElapsedMSec(ltc_t *const t_start); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +uint32_t Time_GetElapsedSec(ltc_t *const t_start); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +void Time_Diff(ltc_t *t_diff, ltc_t const *t_start, ltc_t const *t_end); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +uint32_t Time_DiffUSec(ltc_t const *t_start, ltc_t const *t_end); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +uint32_t Time_DiffMSec(ltc_t const *t_start, ltc_t const *t_end); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +uint32_t Time_DiffSec(ltc_t const *t_start, ltc_t const *t_end); + +/*!*************************************************************************** + * @brief Time delay for a given time period. + * @param dt Delay time. + *****************************************************************************/ +void Time_Delay(ltc_t const *dt); + +/*!*************************************************************************** + * @brief Time delay for a given time period in microseconds. + * @param dt_usec Delay time in microseconds. + *****************************************************************************/ +void Time_DelayUSec(uint32_t dt_usec); + +/*!*************************************************************************** + * @brief Time delay for a given time period in milliseconds. + * @param dt_msec Delay time in milliseconds. + *****************************************************************************/ +void Time_DelayMSec(uint32_t dt_msec); + +/*!*************************************************************************** + * @brief Time delay for a given time period in seconds. + * @param dt_sec Delay time in seconds. + *****************************************************************************/ +void Time_DelaySec(uint32_t dt_sec); + +/*!*************************************************************************** + * @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) + *****************************************************************************/ +bool Time_CheckTimeout(ltc_t const *t_start, ltc_t const *t_timeout); + +/*!*************************************************************************** + * @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) + *****************************************************************************/ +bool Time_CheckTimeoutUSec(ltc_t const *t_start, uint32_t const t_timeout_usec); + +/*!*************************************************************************** + * @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) + *****************************************************************************/ +bool Time_CheckTimeoutMSec(ltc_t const *t_start, uint32_t const t_timeout_msec); + +/*!*************************************************************************** + * @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) + *****************************************************************************/ +bool Time_CheckTimeoutSec(ltc_t const *t_start, uint32_t const t_timeout_sec); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +void Time_Add(ltc_t *t, ltc_t const *t1, ltc_t const *t2); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +void Time_AddUSec(ltc_t *t, ltc_t const *t1, uint32_t t2_usec); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +void Time_AddMSec(ltc_t *t, ltc_t const *t1, uint32_t t2_msec); + +/*!*************************************************************************** + * @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. + *****************************************************************************/ +void Time_AddSec(ltc_t *t, ltc_t const *t1, uint32_t t2_sec); + +/*!*************************************************************************** + * @brief Converts ltc_t to microseconds (uint32_t). + * @param t Input ltc_t struct. + * @return Time value in microseconds. + *****************************************************************************/ +uint32_t Time_ToUSec(ltc_t const *t); + +/*!*************************************************************************** + * @brief Converts ltc_t to milliseconds (uint32_t). + * @param t Input ltc_t struct. + * @return Time value in milliseconds. + *****************************************************************************/ +uint32_t Time_ToMSec(ltc_t const *t); + +/*!*************************************************************************** + * @brief Converts ltc_t to seconds (uint32_t). + * @param t Input ltc_t struct. + * @return Time value in seconds. + *****************************************************************************/ +uint32_t Time_ToSec(ltc_t const *t); + +/*! @} */ +#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 new file mode 100644 index 0000000000000000000000000000000000000000..b99488bb4b4c7b7f8c3d562f4fcb5fa4f74d1a2b GIT binary patch literal 226886 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

=FBBA6y}W#-FPHAYtF$aYZkdd>7# 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 Timer Plausibility Test\n"); + status = TimerPlausibilityTest(); + + if (status != STATUS_OK) { goto summary; } + + print("1 > PASS\n\n"); + + print("2 > Timer Wraparound Test\n"); + status = TimerWraparoundTest(); + + if (status != STATUS_OK) { goto summary; } + + print("2 > PASS\n\n"); + + print("3 > SPI Connection Test\n"); + status = SpiConnectionTest(spi_slave); + + if (status != STATUS_OK) { goto summary; } + + print("3 > PASS\n\n"); + + print("4 > SPI Interrupt Test\n"); + status = SpiInterruptTest(spi_slave); + + if (status != STATUS_OK) { goto summary; } + + print("4 > PASS\n\n"); + + print("5 > GPIO Mode Test\n"); + status = GpioModeTest(spi_slave); + + if (status != STATUS_OK) { goto summary; } + + print("5 > PASS\n\n"); + + print("6 > Timer Test\n"); + status = TimerTest(spi_slave); + + if (status != STATUS_OK) { goto summary; } + + print("6 > PASS\n\n"); + +summary: + print("########################################################\n"); + + if (status != STATUS_OK) { + print("# FAIL: HAL Verification Test finished with error %d!\n", status); + + } else { + print("# PASS: HAL Verification Test finished successfully!\n"); + } + + print("########################################################\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) { + 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; + } + + return STATUS_OK; +} + +/*!*************************************************************************** + * @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. + * + * @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. + * + * @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). + *****************************************************************************/ +static status_t TimerPlausibilityTest(void) +{ + uint32_t hct0 = 0; + uint32_t lct0 = 0; + uint32_t hct1 = 0; + uint32_t lct1 = 0; + + /* Get some start values */ + Timer_GetCounterValue(&hct0, &lct0); + + /* Check max value is not exceeded for LCT timer (us) */ + status_t status = CheckTimerCounterValues(hct0, lct0); + + 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. */ + for (volatile uint32_t i = 0; i < 100000; ++i) { __asm("nop"); } + + /* Get new timer value and verify some time has elapsed. */ + Timer_GetCounterValue(&hct1, &lct1); + + /* Check max value is not exceeded for LCT timer (us) */ + status = CheckTimerCounterValues(hct1, lct1); + + 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)))) { + 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; + } + + return STATUS_OK; +} + +/*!*************************************************************************** + * @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. + * + * @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). + *****************************************************************************/ +static status_t TimerWraparoundTest(void) +{ + /* Test parameter configuration: *****************************************/ + const int8_t n = 2; // The number of wraparounds to test. + /*************************************************************************/ + + uint32_t hct0 = 0; + uint32_t lct0 = 0; + uint32_t hct1 = 0; + uint32_t lct1 = 0; + + /* Get some start values. */ + Timer_GetCounterValue(&hct0, &lct0); + + /* Check max value is not exceeded for LCT timer (us) */ + status_t status = CheckTimerCounterValues(hct0, lct0); + + 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; + + /* Periodically read timer values. From previous tests we + * already know the timer value is increasing. */ + while (hct0 < hct2 || lct0 < lct2) { + /* add counter a , which is increasing by +1, 1000000 or 1000, + * different MCU different times get stuck for hard code value */ + Timer_GetCounterValue(&hct1, &lct1); + + /* Check max value is not exceeded for LCT timer (us) */ + status = CheckTimerCounterValues(hct0, lct0); + + 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 + * than previous one. */ + if (!(((hct1 == hct0 + 1) && (lct1 < lct0)) + || ((hct1 == hct0) && (lct1 >= lct0)))) { + 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; + } + + 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). + * + * @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. + * + * @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) +{ + /* Test parameter configuration: *****************************************/ + const uint32_t timeout_ms = 100; // The transfer timeout in ms. + /*************************************************************************/ + + status_t status = S2PI_TransferFrame(slave, data, data, size, 0, 0); + + if (status < STATUS_OK) { + error_log("SPI transfer failed! The call to S2PI_TransferFrame " + "yielded error code: %d", status); + return status; + } + + /* Wait until the transfer is finished using a timeout. + * Note: this already utilizes the timer HAL. So we might + * need to test the timer before the SPI connection test. */ + ltc_t start; + Time_GetNow(&start); + + do { + status = S2PI_GetStatus(); + + if (status < STATUS_OK) { + error_log("SPI transfer failed! The call to S2PI_GetStatus " + "yielded error code: %d", status); + S2PI_Abort(); + return status; + } + + if (Time_CheckTimeoutMSec(&start, 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); + + return status; +} + +/*!*************************************************************************** + * @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. + * + * @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 SpiConnectionTest(s2pi_slave_t slave) +{ + status_t status = STATUS_OK; + uint8_t data[17U] = { 0 }; + + /* Transfer a pattern to the register */ + data[0] = 0x04; // Laser Pattern Register Address + + for (uint8_t i = 1; i < 17U; ++i) { data[i] = i; } + + status = SPITransferSync(slave, data, 17U); + + if (status < STATUS_OK) { + error_log("SPI connection test failed!"); + return status; + } + + /* Clear the laser pattern and read back previous values. */ + data[0] = 0x04; // Laser Pattern Register Address + + for (uint8_t i = 1; i < 17U; ++i) { data[i] = 0; } + + status = SPITransferSync(slave, data, 17U); + + 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) { + 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; + } + } + + return STATUS_OK; +} + + +/*!*************************************************************************** + * @brief The data ready callback invoked by the API. + * + * @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 DataReadyCallback(void *param) +{ + IRQ_LOCK(); + *((bool *) param) = true; + IRQ_UNLOCK(); +} + +/*!*************************************************************************** + * @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. + * + * 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. + * + * @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. + *****************************************************************************/ +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 }; + status_t status = SPITransferSync(slave, d1, sizeof(d1)); + + 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) { + error_log("Device configuration failed!"); + return status; + } + + uint8_t d3[] = { 0x18, 0x00, 0x00, 0x03 }; + status = SPITransferSync(slave, d3, sizeof(d3)); + + 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) { + error_log("Device configuration failed!"); + return status; + } + + uint8_t d5[] = { 0x12, 0x00, 0x2B }; + status = SPITransferSync(slave, d5, sizeof(d5)); + + 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) { + 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) { + error_log("Device configuration failed!"); + return status; + } + + uint8_t d8[] = { 0x0C, 0x00, 0x00, 0x00 }; + status = SPITransferSync(slave, d8, sizeof(d8)); + + 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) { + error_log("Device configuration failed!"); + return status; + } + + uint8_t d10[] = { 0x20, 0x01, 0xFF, 0xFF }; + status = SPITransferSync(slave, d10, sizeof(d10)); + + 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) { + error_log("Device configuration failed!"); + return status; + } + + return status; +} + +/*!*************************************************************************** + * @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. + * + * @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. + * + * @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) +{ + // 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)); + + if (status < STATUS_OK) { + error_log("Trigger measurement failed!"); + return status; + } + + return status; +} + +/*!*************************************************************************** + * @brief Waits for the data ready interrupt to be pending. + * + * @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. + *****************************************************************************/ +static status_t AwaitDataReady(s2pi_slave_t slave, uint32_t timeout_ms) +{ + ltc_t start; + Time_GetNow(&start); + + while (S2PI_ReadIrqPin(slave)) { + if (Time_CheckTimeoutMSec(&start, timeout_ms)) { + error_log("SPI interrupt test failed! The S2PI_ReadIrqPin did not " + "determine an pending interrupt within %d ms.", timeout_ms); + return ERROR_TIMEOUT; + } + } + + return STATUS_OK; +} + +/*!*************************************************************************** + * @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. + * + * 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. + * 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. + * + * + * @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. + * + * @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 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) +{ + /* 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); + + if (status < STATUS_OK) { + error_log("SPI interrupt test failed! The call to S2PI_SetIrqCallback " + "yielded error code: %d", status); + return status; + } + + /* Check if IRQ is not yet pending. */ + if (S2PI_ReadIrqPin(slave) == 0) { + error_log("SPI 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) { + error_log("SPI interrupt test failed!"); + return status; + } + + /* Trigger Measurement. */ + status = TriggerMeasurement(slave, 0); + + if (status < STATUS_OK) { + error_log("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) { + error_log("SPI interrupt test failed!"); + return status; + } + + /* Wait for Interrupt using the callback method. */ + while (!isDataReady) { + if (Time_CheckTimeoutMSec(&start, timeout_ms)) { + error_log("SPI interrupt test failed! The IRQ callback was not " + "invoked within %d ms.", timeout_ms); + return ERROR_TIMEOUT; + } + } + + /* Remove callback. */ + status = S2PI_SetIrqCallback(slave, 0, 0); + + if (status < STATUS_OK) { + error_log("SPI interrupt test failed! The call to S2PI_SetIrqCallback " + "with null pointers yielded error code: %d", status); + return status; + } + + return STATUS_OK; +} + +/*!*************************************************************************** + * @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. + * + * 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. + * + * @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) +{ + /* Enable EEPROM: */ + uint8_t d1[] = { 0x12, 0x00, 0x4B }; + status_t status = SPITransferSync(slave, d1, sizeof(d1)); + + if (status < STATUS_OK) { + error_log("EEPROM readout failed (enable EEPROM), " + "error code: %d", status); + return status; + } + + uint8_t data[16] = { 0 }; + + /* Readout Data */ + for (uint8_t address = 0; address < 16; address++) { + status = EEPROM_Read(slave, address, &data[address]); + + if (status != STATUS_OK) { + error_log("EEPROM readout failed @ address 0x%02x, " + "error code: %d!", address, status); + return status; + } + } + + /* Disable EEPROM: */ + uint8_t d2[] = { 0x12, 0x00, 0x2B }; + status = SPITransferSync(slave, d2, sizeof(d2)); + + if (status < STATUS_OK) { + error_log("EEPROM readout failed (enable EEPROM), " + "error code: %d", status); + return status; + } + + /* Apply Hamming Code */ + uint8_t err = hamming_decode(data, eeprom); + + if (err != 0) { + error_log("EEPROM readout failed! Failed to decoding " + "Hamming weight (error: %d)!", err); + return STATUS_ARGUS_EEPROM_BIT_ERROR; + } + + /* Add remaining bit to the end. */ + eeprom[15] = data[15] & 0x80U; + + return STATUS_OK; +} + +/*!*************************************************************************** + * @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. + * + * 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 + * + * @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. + *****************************************************************************/ +static status_t GpioModeTest(s2pi_slave_t slave) +{ + /* Read EEPROM 3 times and verify. */ + uint8_t eeprom1[16] = { 0 }; + uint8_t eeprom2[16] = { 0 }; + uint8_t eeprom3[16] = { 0 }; + + status_t status = ReadEEPROM(slave, eeprom1); + + if (status < STATUS_OK) { + error_log("GPIO mode test failed (1st attempt)!"); + return status; + } + + status = ReadEEPROM(slave, eeprom2); + + if (status < STATUS_OK) { + error_log("GPIO mode test failed (2nd attempt)!"); + return status; + } + + status = ReadEEPROM(slave, eeprom3); + + 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)) { + 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); + + if (chipID == 0 || module == 0) { + error_log("GPIO Mode test failed (data verification)!\n" + "Invalid EEPROM data: Module = %d; Chip ID = %d!", module, chipID); + return ERROR_FAIL; + } + + 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. + * + * @details The function reads the devices EEPROM via GPIO mode and extracts + * 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. + * + * @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) +{ + /* Read EEPROM */ + uint8_t eeprom[16] = { 0 }; + status_t status = ReadEEPROM(slave, eeprom); + + 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: + + /* 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: + + error_log("EEPROM Readout failed! Unknown module number: %d", module); + return ERROR_ARGUS_UNKNOWN_MODULE; + } + + return status; +} + +/*!*************************************************************************** + * @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. + * + * @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. + * + * @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); + + if (status < STATUS_OK) { + error_log("Speed test failed!\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) { + error_log("Speed test failed!\n" + "SPI Read IRQ pin didn't raised, timeout activated at 200ms, error code: %d", + status); + return status; + } + + return status; +} + +/*!*************************************************************************** + * @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. + * + * @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. + *****************************************************************************/ +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%. + /*************************************************************************/ + + /* Read RCOTrim value from EEPROM*/ + int8_t RcoTrim = 0; + status_t status = ReadRcoTrim(slave, &RcoTrim); + + if (status < STATUS_OK) { + error_log("Timer test failed!\n" + "EEPROM Read test returned code: %d", status); + return status; + } + + print("RCOTrim = %d\n", RcoTrim); + + /* Configure the device with calibrated RCO to 24MHz. */ + status = ConfigureDevice(slave, RcoTrim); + + if (status < STATUS_OK) { + error_log("Timer test failed!\n" + "Configuration test returned code: %d", status); + return status; + } + + + /* Run multiple measurements and calculate a linear regression. + * Note: this uses float types for simplicity. */ + float xsum = 0; + float ysum = 0; + float x2sum = 0; + float xysum = 0; + + 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); + + if (status < STATUS_OK) { + error_log("Timer test failed!\n" + "Run measurement returned code: %d", + status); + return status; + } + + uint32_t elapsed_usec = Time_GetElapsedUSec(&start); + + xsum += (float) samples; + ysum += (float) elapsed_usec; + x2sum += (float) samples * samples; + xysum += (float) samples * elapsed_usec; + + print("| %5d | %7d | %10d |\n", i, samples, elapsed_usec); + } + + print("+-------+---------+------------+\n"); + + + const float slope = (n * xysum - xsum * ysum) / (n * x2sum - xsum * xsum); + const float intercept = (ysum * x2sum - xsum * xysum) / (n * x2sum - xsum * xsum); + 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) { + 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; + } + + 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 new file mode 100644 index 0000000000..1ac16fcaa5 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.h @@ -0,0 +1,166 @@ +/*************************************************************************//** + * @file argus_hal_test.c + * @brief Tests for the AFBR-S50 API hardware abstraction layer. + * + * @copyright + * + * Copyright (c) 2021, 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_HAL_TEST_H +#define ARGUS_HAL_TEST_H + +__BEGIN_DECLS + +/*!*************************************************************************** + * @addtogroup argustest + * @{ + *****************************************************************************/ + +#include "argus.h" + +/*!*************************************************************************** + * @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. + * . + *****************************************************************************/ +#define HAL_TEST_VERSION "v1.1" + +/*!*************************************************************************** + * @brief Executes a series of tests in order to verify the HAL implementation. + * + * @details A series of automated tests are executed in order to verify the + * implementation of the HAL required by the API. + * + * The following tests are executed: + * + * 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. + * + * 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. + * + * 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. + * + * 4) SPI Interrupt 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. + * + * 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. + * 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. + * + * 5) GPIO Mode Test: + * + * 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. + * + * 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 + * + * 6) Timer Test: + * + * 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. + * + * ------------------------------------------------------------------- + * + * 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. + * + * + * @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); + +__END_DECLS + +/*! @} */ +#endif /* ARGUS_CAL_API_H */ diff --git a/src/drivers/drv_sensor.h b/src/drivers/drv_sensor.h index 7f6833741f..c4c9e739c8 100644 --- a/src/drivers/drv_sensor.h +++ b/src/drivers/drv_sensor.h @@ -1,6 +1,6 @@ /**************************************************************************** * - * Copyright (c) 2012-2020 PX4 Development Team. All rights reserved. + * Copyright (c) 2012-2021 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 @@ -163,23 +163,23 @@ #define DRV_MAG_DEVTYPE_UAVCAN 0x88 #define DRV_DIST_DEVTYPE_UAVCAN 0x89 -#define DRV_ADC_DEVTYPE_ADS1115 0x90 -#define DRV_DIST_DEVTYPE_VL53L1X 0x91 -#define DRV_DIST_DEVTYPE_CM8JL65 0x92 -#define DRV_DIST_DEVTYPE_LEDDARONE 0x93 -#define DRV_DIST_DEVTYPE_MAVLINK 0x94 -#define DRV_DIST_DEVTYPE_PGA460 0x95 -#define DRV_DIST_DEVTYPE_PX4FLOW 0x96 -#define DRV_DIST_DEVTYPE_TFMINI 0x97 -#define DRV_DIST_DEVTYPE_ULANDING 0x98 +#define DRV_ADC_DEVTYPE_ADS1115 0x90 -#define DRV_GPIO_DEVTYPE_MCP23009 0x99 - -#define DRV_DIST_DEVTYPE_SIM 0x9a -#define DRV_DIST_DEVTYPE_SRF05 0x9b -#define DRV_DIST_DEVTYPE_GY_US42 0x9c -#define DRV_BAT_DEVTYPE_BATMON_SMBUS 0x9d +#define DRV_DIST_DEVTYPE_VL53L1X 0x91 +#define DRV_DIST_DEVTYPE_CM8JL65 0x92 +#define DRV_DIST_DEVTYPE_LEDDARONE 0x93 +#define DRV_DIST_DEVTYPE_MAVLINK 0x94 +#define DRV_DIST_DEVTYPE_PGA460 0x95 +#define DRV_DIST_DEVTYPE_PX4FLOW 0x96 +#define DRV_DIST_DEVTYPE_TFMINI 0x97 +#define DRV_DIST_DEVTYPE_ULANDING 0x98 +#define DRV_DIST_DEVTYPE_AFBRS50 0x99 +#define DRV_DIST_DEVTYPE_SIM 0x9A +#define DRV_DIST_DEVTYPE_SRF05 0x9B +#define DRV_DIST_DEVTYPE_GY_US42 0x9C +#define DRV_BAT_DEVTYPE_BATMON_SMBUS 0x9d +#define DRV_GPIO_DEVTYPE_MCP23009 0x9F #define DRV_GPS_DEVTYPE_ASHTECH 0xA0 #define DRV_GPS_DEVTYPE_EMLID_REACH 0xA1 diff --git a/src/lib/drivers/rangefinder/PX4Rangefinder.hpp b/src/lib/drivers/rangefinder/PX4Rangefinder.hpp index f666594a6e..6c6b87c0eb 100644 --- a/src/lib/drivers/rangefinder/PX4Rangefinder.hpp +++ b/src/lib/drivers/rangefinder/PX4Rangefinder.hpp @@ -48,7 +48,7 @@ public: // Set the MAV_DISTANCE_SENSOR type (LASER, ULTRASOUND, INFRARED, RADAR) void set_rangefinder_type(uint8_t rangefinder_type) { _distance_sensor_pub.get().type = rangefinder_type; }; - void set_device_id(const uint8_t device_id) { _distance_sensor_pub.get().device_id = device_id; }; + void set_device_id(const uint32_t device_id) { _distance_sensor_pub.get().device_id = device_id; }; void set_device_type(const uint8_t device_type); void set_fov(const float fov) { set_hfov(fov); set_vfov(fov); }