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 zcmeFa3w%`7wLiYkoJlg740-Yh0mCHWkwAbv01>(EWHLEC1cCvr6w} z-h2PQ_H#F!>{)xQz4qE`@4e1`pNzQ`-YU-*tkdn0E`L_u%-OT@W)u|K?Rgxf|7*8j zUzo23*vqYqvA;5Q-A~kK{I41NpL$B3VvL?!UJpOFf0ePX{4MgSDq!qt&!&-#UFmrq z`P4s0UuEp4FEe)Hk)fY63S;#2eBqiOspK#0|GMYslS~rNdtN5}H$0}rnQX|XDV~|c z^9?IA(euFHBA++kXQpd?{`@L4{qOU6b>OL3v!vQRHh635 zRPnqkm${pYD{5;iE2>s=fqBS+x+b^xu8LX_(p2FM(KppMup;lB&5bJ;yDJ)-z3w&c zx+XQL!NEn9^@uq*d_iMTRa4Df?jca9Vb12p_0Hy1tK8nkAqbX10#dPeNE}HzIKAcG znmg|tGKPj(w!W@vh)ISi4@D(dcZD-~5u^&y!#>XwG;3Y3N_5f3S9 zXsBJk)axdoxuU72zHShJ_iLQ;SAHs_4b_b*pOh!CZbl_vPM-2KR>gnrhS+T|S{$=%l%} zLX;D@>r*9=Dj(XjU{#|bGqKZbhSE;C}MKjk?Yn3w; z#pz) zVoJ5%n2_EcQA{$2u8O7#9e<cf$IYKyLwP4Pv3cGt?r$xi^H+E zHa1sY0gac+U@Tt!+y)IY9)l4&Gpa_KS5Z?JVYMiZDCN+*fC61l2Q-RfKMGN|`1uc!=F zdW|Gp9W(>3OP-rd#p_<%jPCM!og~He5UQ?G|7s_8oF0XM-v&diB{r|NzOtfLC$BNF z%f?*lft^@0wxA`=d!0kk4`x~Ht#52J6x~(0Ub&>j8~t*HqrIhvAk+20 zxs43M&^mQ@?yBY{H??wrq6#fj z>z|&fM#%GfO_aJScTMz5Yioz3b1hq`y7gmNGp9DaEtrrwqU9Bf5(MySkPLn9+$h;_hXRLpuY5J2M= zdUc`K=&fHrzoL#y%7;J2UN>G-5kb1VauP19&g$w%5;Tmt3`!f5=&+is*Hi~IqRW*M z7wp>Vh(=Y00Wb~LuBo1Aaf&J%>uXV6uPKhcS0?(Fy4t@Ka}t^Ypx~mV3nKd*2y-^q z)K)KPUQ-zw;vrCNdxK~PcUnMe?64pxqQe7t+0q5eYAZrRR)j>nr1kZWt9Wn?#B0vw zu57*&f{A)X>0EV40U}{{^n<0aN`@K<(K~erYGbgjjv2(#M;@BW$jjIdrp)QN81csV zE@kl*WQfEX%CWCbOq8uT-Tt7y|n&rw|5yzzNC(N2-TO-`?4$08u_}` z*Hx^ksVb_jXwZ#9jrtmz%E*hqu$?t^brG#3Lh;u?atj2ithuwfzPXV%SuK;s#PkC~ zZp!ryE+Wm#QyO{FYhUYChh7Z41sU~mB|_0a!f!@hF-5kpAxQ=Wl9Yo2)iD~z#Dh_h z^q1?)QI^77FGm&0>vG-LNM6@Qwfsu`Vm_Iq6Yp{?BgNBMc+nYAC!6#G+(=okDN1B2 zLv{FcB^=i2H4}1GT!GrCjVq8|M$~InM~L&~BU~kgA~Er|+~ajOdg^QSMrsJ%h=eX1 zFETyqbBZWeE88p6M)D11oY%Ah{`bm-swbRoT*{1G*c=4qUtgewm0K&gfmwqI?3EP_ zHPh;+YawpTJ`54kfa%b|bWP!jPXmXh(A;MjQH5E{YITB97l`DB40O92FlRx8R&5yu zzg6)sw_=;0mp5zHEW17b`k7ir%dPj8EK(#%!oscUB$my>5Mx0z509F`y=-P$6k~JlDy9` zsoP@CDwbwSL0jIR58XShtD`i);!6WkY;1r0$I-o?%`NTzt;H6Iu_Xm94YB>Ui+zVC zb;~iHbP4bp)^_O6hY-U$BlU6TX5~n$NlAS7)r&uupX)gh_sct^g5v(Tizg3JTFYEd zI#>2@?%%Hbr8C>rcIX?~AD*=$T<*7h8s96$&+E3?e%c$80KRQ)9mhWke22ACj=pt3 zR@iuHOM+s0dbE<5`PO-p>#g%<*JpqI+h{oPs01^E8W+ACR2&n@bXkN*kem4lY9V;$0-?*%RPG3yXB3Un+x z?bVA8^NEWNDYaFyKkR(~Oh0w#tR?z+n`>Xs z&!pkO#JQ&}aYd$QOo14aG$HmYl!KnM?>e?SGPnHPBkIYX6LNHa{HK{pVtZ;Zaqp;Lly5=5%AEc;jO2mNebaq%2`_Piyx*q7JW*EZT$)qCwl-Zl0T^#7}?-PQJ` z-ufzcV|RXh(Zr^`;QGS6g6qG+iVD$2D%=&7HB8FEE5rY>gD!(tZ4nyk^u z=Hb!^X_TBQjh51+G16Em-IS?sX}p0&v^~bm1~^J3(*DW@tjtUXyljBg#w-G5X5<$m zP~4Z91rjix%1QXifKoL0R}eiJ@iRH4`X`aR@lBy+;h|TF*SObIHLNGnv3RUD{F&Cc zeoa$FCHy9@>U+emCil7~HVxCg6>M5%VGg8o6o*!*PYiGc-D(aU-25=}Dlo;JF%IuEtNF?rDSx zCrYve0ebl?0H|z)6Ldugh{fa35cdEWN50Sz!9LKn!>6r6^iaA~Z%7WHLT)J#)QE%={E`r$r)vS7zBmP4 z6INS7FGRc+ul7!yT{vU5`u4o~JMo@WE>-BAID-{3={me6T3IVQy!n9ij7On&2HR6~ z;Xh2P*Z+(}2w6178W62TB8<<*C14?cJ~<#~fuBR=$6Cid z5yLjWKc+bE@Gxcb0W)H&d0g$0b|YtJPNuPpY!BO0Sw1FhMOs$5d?4?*sjn&1)R*Wz znI>ff{H!(cuvys@O1a!4?b+!ZeI_-HWo3BS?y|H~X{x@WHGRcnxFR?K-P0H#D=`50el}FmS**Wgai8N-<^srq?iyll%PJ7ZBbv9wagU*DG z1ZdHACVogQ>eFImui|q8%Pt{7;)wlw83`UJ&9?KG}5j$q+Lsd zb}1d9rBl$-hyIYBU%JV&+_Mjw{Vd9O6gGxs%(H}@qreV8Uq z2>26UD)uY_?HHoXvU->&d5|tGkYNXEUhI4}AUja+ZfclE+Ejn(_+`Bx(r2+}p=Y}@ z@1=2+p9!%ZlZSLzHs;!NNODpNV{VXzL>bP``0N$HFy`4iLF7}8r@NN=$bdQ+v= zaUK5$)GwjChL<|dv*MHIncK2pife}JA!i%l=(GD!YxbemRE`Cl)d0Cz>le}1Y&Ye- zv~R$4B5sjw@gAFbASDy=hiAItT+dR>g6OkPq9#9ynk*F(OgW;qK<$*`54)*>;uj;< z$UU)Wkxj%ScCmTDRCzfblsdIViXZ1mbD7F-@)Vah%nKZjQob3oAX64i^TZ!r?MZo2 ze#Z1PwO48PY7g5v&6DEV=VU$6k~x$sDgSAaMLU&Jh~SrnI*oagIeCDU)SxLQj-U{(1YK{JWhGg9`%}$=2zYckOb{ zXk}U8Fy%=5IUW3tU*1EsEK|z-0Q#rA1P))nT}NM9?7K3MZts4e8?wqL3tS2{vP_(J4$ZQgHCoIJ;EvIIL%?@}P4(#& z{Yrbc4(`zEWSl4E=uI9r#o69)*oHSyROszh?y2)+dL}N)@#MK4cS=1bdHk7O&Kyd2 zJR8q;HyzyV%q7VnXZEqJ&IC;&szs&)4aaw@ZHwpPqK9Y>rBB}6k=d8gfGRc1?(6uqUl0Gf{T)sIhQkGmpK)$FU{j)%iD$*T zX4*~D7QAzwRqh)w%X@6nKuS@P>ltUE%YNoX=S)pzwl{ciuX8Rz87$+5V=p`=v+2qceN7op^d+v93eWiWI)`g<90y-=et;Zq(e(XWfVI%z45Xja3Yct!XhRK$529UAzM_ zj;H}f<&*m&+q>84(xmeodpJ}YB6rj~Vgu$&pN4YzHP6@uQ#_BS z-tSC3J6fUgq0*5GPx*=0$WTrncAnMc^c^jyJm)CG(F)5w*WQZq5;lWn|E2xI2>6d3 zFQfcKn~6P2rJjeL7v)yy9Qn-3n*T)VW~bIx^3}GoRn5r+Hi7MJKKO)lidOHA9DOpP z4pJ}c36zg$NguaA$?G6*9cmr46U|O4lL^v<|Kpc--5;uPAGG@;;Ey_f!^=nZsQs|` zP(RG!p?<5Z^;^~Vdc!=2N9H{#&AI}ZwQG{2>HGM|wMhsc=i z);pQS>p{pvGZ$eeV@6ExWHww6f_{Vn#z7;Uli4U8MEO-G8bqMXMvI@^mr)<}PG&S) z6?QV8VbjCuVF15mbiVXXj+o6Oe7GctPG%GJjt1v(IF)^%!43|OW`Bx+ z)7U2(Of@kXDHgWEVfo-NVfGaR1C zsx?^Um(4b4@ber$nSDotRek2NJsPa)e=7Td2Iq2mlrQFk$A{1Jh@(MXzNwTpIJ>=k zizuU&eF_9c_&AFShmW+VaOmUIJ5SH?0+mChXzyEhv@S0 z5Dh&9D|qz$=)FSk65#|tCj#{REINMFHxT^F@mtTY1@IksgcJNIOwTV-$B*O^{5IgX zo?jPWl2JIp?=A%B`K9Xkkv$Xqw&S;+-&g=c@%xT}pIyg~?62S_`X8hNa<_qBIsc{p z5%N6)P%mG$jvw`E1;1b7w_d&@0Gd=*{IB5mb3l52c{+ZHzzKfm@mtR?7mI<^ml96! z`y&GM{ATO;k=+;kOkk$x*8+Z2*Mt-Nk_~hRE|cy=1DyqniypxYzTh{_B{IXH8sV^&> z;P(J5WN6=1=pv~%x zj*xat1nuw$T4-LUm*`GA^vW`hf7GCA_PDBBDTC>B`%~y9oWr&YFGuLA?ZOKNT|Q-? z|Gt6#Q3L%Wfc51`LD z)oGAmeQO|?T7#t3euB68q5=sfeaHg_ijBH5kki*g-f(*Q)*z>^*5yS`ukOS;>-NHi zm5*r4Vts`s;*#ht6ic6I2}n-h^j zA@mcLJXfbzIekKeoaqtqMn&Kf0STSU>9JZnP^B=bR!T`#WUNp#*c-j}Z;jwn8o_5i zAYp~%^r8q!=X8?J!`Old>=a0#ramzuN8>tUCdBJ(C~3&94A<;vB6OA5uJlJ;p#8w% z{0M7mSAGX;iuGb$>3tj_<9|j-@li;MKSbp6w-LE~J3?E(?i4lkpJ_df-v9LOr172! zy#e|icF~xj)$l-=&Inh&4ZTiFriGY(-y;#vtTVAC-JX^KXNn4w$+`6i#Cv@^dwx#_R+y z(tN641lMXl%^MJWzve%x`TI5hkmkRt`R$tjzUKc$^FP&mEA&hJ(lnp+P4FzucWM5u z@c$mU@6pnGSo5iTDV?V^|7Fd8P4iD`ewXH7PVQ7qzRSs7rs2yqzfSY-(fmi?Q@(h) zp>Ld~r6*Q#&O39h(2V=D(r&?`S^H2l(-FLHtj(a8aK`xln%6Y5DQ<@j#^~-%(9NMoG~<8V{r!1RHB=rN_s_s@s1|S<@WV?xm z9;#D|@aXw%0ZesJIKgi|0`&Z5062m$;)iMy;fZl<^34V7XK+o?_0L~zc z_>o*fzHj2Uo}W)<%r^wTjiA%>dkqb~4PnF&KSTWX;J2P19bzEcBAk%#F$C!O(fq*U z2qS(rJVL$~@SCFN;UP!!PD9NP{1`Zh-oT0Q=ifj_Wha~vfqXQ#kUI^!EeND>KAJ60 zmt-K>z%S&gp|l@_AXO#;jq?NJA^gvWpi}1u>h7YGfpnhXYUdvE^Yd{cklZz#dr&hJ znjfIEfb{_jqy1k-|DUN3Dauq;cNDKDVb!Bxb?)!)^PwW^oE)+cYM{(_M1%?SXS7HX z+95OyKFO@YQG8cP7)B`iXji)Z@pHWc_=;hOo4U|NpxVz0!@Lvsk87{V4htRPCvWp) z--HwPhWgify19UdqPxjJ=ei8t?HZl1OJwJVvP)G6*W0BNfUm_a1wj|UL$+1e!=dcb zN63^NG{R9H$r95vE^VpLv@(AQhnt+4PhtLn_xe`MNs4Y^SAfen$AS~r1m~k z`z%7ZzJ0z3_*&ZMN1)q+hsuN=Dx;y==QeOZxZe#$U|!inEBE~Rp%awg}%qpvN3t`6aPy7_-yQ(e#U&=D4tZPPcsdBr!D0T^JontUKxRVG)gadytLBr+WB*C(|q~`TY8*g zwFh^~fLpPVC6Dd|q48O9O?&WkM(S9Ctp^=2$qn;nA7&HGN>q7!a4*q+=JcFK%ra|^ zV(NR;D^_V)>ujV+_^kE&UVJTsXb6f@&K%9E`TEhSnv`Sa-r;gqg$b*Tqsueb64s7& zFLjmIG=X4>^;!n~%EqcNL9Fi(UoB$Uo$VBU!GAh25a zU7Nnoxfds7v(~C>Hx!pmpx6`2Rf%~B-4Rn{Q`p3gV4^R+B1-8%E=~t`G^+U;(d$hZ z*8c2uaqWkl@!I;!Uk?^x#rsC+B2bh%&#uhm(p^MORUJ+&7xD?+Qy%g;)pKJ%=wMmd z${RtJ$4`9na$q&)Jd!D^Cevdsr2J6MHp8EDQ1Hs~I6Rwi+d!5QzmRL1>pcpqW3!Lj z%QHClsPzAQH%Ez?FLlMtS9L~f^PoAUpvU(XsH6HGZLSYQ z#pEbz+K(T77dm6x*&frBOYLko%i8Tc(#m!dhgVv&Dv*Mm?Np`6Q5;-e_83vWhZAvZ z&m^MA6BJ=x$T)|ccv#9ZD>AR+S?h~+?V%m5A?sLG&32T->HGn$9vj<4B43|J{#w;G z^A}zh!foNV?Z-a~(P*tkgs|*HoL@D+n}KptX<}=rd~NxpuS~_fK-4QaopmN2PgTzQ zsTe6%;z5>eL6}t`ny3X7gJplLE<)}-ImabAvcT1j?(fb}FMntDu`ERcXu6HeQs*7Pt(ay;8%>Pf7$=Wk;n zeM6ofBTbShw2B&PL$eRJ1$U&F70oVx7L}gdDOwJ-)zwEutIYC5DJu?!EfHBiDs9!0 zsYT#0`*zXBc9^uY-2gASjE_doDZp7)EWF00NQ>RVIaWbnFl6ki{*Yt(c@ z51ss?euoc_2{U@44+0P_#sJ3}VB8hYJZ2ZN%%!!>+qN`Lmi1vFu+Ot{1nlA zgVQG&@F`sWTHsWd5?Y_2@v;fIiR3?m(}x3%=Rl459nI;(LqBAcCXLm?j!HM|=-gf;KCB6%`mY~*Vl1e0G`7^c!RIci z=N@)4pV8LCxF1R9WZq*B2grPdMGs@aRp$nuv!vD)c7xARUKd8=sTxjJas$5H z0MkBV;fUY826(Fhehe`2r*-^fHi8{C;D2d=-!Z`dX@IR5m+Pn1u3|=zR{x(;rGq! zu1VJ8XoZGfs;=X}>FJvko(g;sskYHbe;JhiYbH8w>UTZVTcEXTTyT}?TtaP5%v|!xL3JL<>eBX$l`v*UJ2qhn#SA}vp0-a6i+Y&^FH*y; zOQ@}5UcOY_S72Srn1f*W3(3bsJgx6pVH7I0aO!^%p7t}P<26Y3o%oQAq3~4lkS%_ATDW|I&3yjr+V3xAmWIQ49a z6`ANFn1q7SG{N?o$)BX@8A9{?K(9Ri&B}|xj4=IJqKDpc z;hR7w_!CUwv&cu__sIv}BO2W+V_y-J6~i8pT;d0FP%Oor*W^%+9nx`erv(!a68v5i#vm+_EPWFoIhq&J#F za*2404@nU+#bNAVjp)qpG;clK@H=fVk?!q6mjmZdn}H%!iFLWx3Lr%B_a3fB^Ffp6 z{k3TL0qOa@0e%#p_{HEMI)cB2 zN6)VsFxfES1iz;dpr>m9Otw@wL3auPdb$?C*@6hZp!*Ge>*Z^Qe2EAn`QkJ>l8?q$ zdVbFXCZ56xetihg^Xme?`3NI^w9ks@h~F46)AP#%FciO02-ow=P7d>Etv^^M-2RC06}d; zIKhwfsxJ>8_}M{3^3kk-;78+K{14@~76@vq!U=xw8u&d9exkjRO%eRe(3hUyP9TPo z50ga^@*UCfqge^TFUP>I9f+ain_%GAuH#3u8iLR zt2OY;h4E=42AUK6JO+Mb3`Bj-00?z;C%=rqL02pMhVVj^6~0j@s`JHGTyY z3C;lnY%C8duXhb}>p*vm$58)}d_?!DMkjPu2;+FSj*e*Pi9$Wc{|1427IZTNpwSRW zv|t%>U7#Zwsf>&9P`ZhDs4jzF$l1|%d_)&Oe8`b)&;_${=w*a1K+sLk*NzWsi`-Y+ zx0jFe#_F>8)%OKGO$DGkZ=8k}M{CveEsozDct|lTv0GYj4$sDKKBiw&`LAQavG&u! z+hpsuB&>wfI?u7-@WiMs6h;u%HABCT1xMO<1Sb?CPGTp%M%5$l)Ft;6%cdTuX$(GV zu}NOxlJeQ6F?#Rvk{PY0iELu6z{WcQwv7Q`V}WfdnGNjQxc&H77e|=K zBED&dBhc%6ykwHXF8tXSz4sL-QJD6=9kjLW@3C^4NeZZuO1F@PZE24ZI%a!h?Lb|5~pIGAC#J(oBWz9VMOii7);wk659O;sK+ zU9dezp;PIvJ!%h|;s_k}$$R9UYFXZH-XrD9J(aRO$Gn|%bMGbZk2-^id7Uw=_tuS4 z)pvWcbL^JibLY*O9|S`L`@d@B$iwRd!BfW?(||I!qPyMuQGT_#f?(KMwvrC7>d z@~o+6v&r6Wd!{Vdl6!M7D{)iz?;AIE|L&gWdwzG%zMkJVR^jU-%jWJZ*&kG1bp-6} zp0h3BVD3w3cw^p6K}W&99;>_`eq65Sa^y)nI}m{&S*+w_4w z!PzD$JEJ{!*W%sa-bAVF@A+L5rI6*iBW`EObHO>zThB;bQx9*9>$8FXd*@^GPib}b zSa3!`YT9$b8KzQN9siDL;t0Q5C$Kgijan1`#Y>l7Fii}eGfhnMv)m)C)y}65m39v= zj@oM;i2m4&S}pgCEPnY=bdRjOg!{AcJssTNJ+jYNI=eNx&!?1jrxcfV7Z=ZHy%dLf zKVz)xm0l^q_FO7BQCW_|UA>!~8&5{<8SRSRljgd|Px?SvnSOvfzh4Y)II%sre)Fc) zET{dv?GL5hi{`dnIO9V)Go(ql8N77`z5tY@eDBZ$hmN#PQpVV#2Bb?-1JQj^J;O`n z0jW2s|C_z?Ks2NrQ4(iP>2JB1!ex{De8o{cNoWV1Bv;`Y^AwjfJr(@E?2OuK-oM_9U4GqWZ{Q<$ibZ_Nd*| zGCF8U>iKq1w|ACSNfDUo!dH?+-Po9b1^N=&Q)tnWb z^*#Rrr!*dAcKrl@bk?B|REGP%&AU}Su*pZD<2|XY_f67zfi(W7xP$#j>#uw9wWjWr zq9d)tl|6^X^*?d}lJ%8#r!Rr#q45#Ucu1Pkzv*K9fb_Tcp5&smPzgvqDaEi;CNY zwO?VVUoz@f46k4KrU6p^=0!FTgRfwWC`mOZ_O}G0kZK~zA8ESgv@+B)wT6v#nfK(? zQthX@&!&926ZxDaO$etluHW}5r85)h*aqZ}Z38i%+ImJVQqy^ur!&qxs{g)^Su@VYP;}DADcR^e^Y;Hck)eb7hdt@;>%ig+x|n= z0duc)fT89jEs4WhW_bT2m*Su))|2ii?KUq-@88&))W`0f-7595wXA2jV@B%(*3wHp z$rmL}vA9xEL+EP(Z5RIC_d5mp_=1#C+C92>T>k@~SO;P+SqJd_uAbow;|5|s8QuTj zCvm7V_(TsnAM@95b^F&o(c@qHNXaAJF)_1S5jW3tO8Q-@ z(u>#&;@6gxb~|Lmo-(_g+GX2?cYMswy1snyk&?ZwPxQn~sl_WSrQJnRU&q>#>{cm{ zO{ca~%$&yx%2C%l)cs_^@6qrxIToBX`e`ti&kv8k{2GArE*k_+wOjS6gm|5v>% z7A+9pY5OeyOMW)J?ZQJoOg;ym_CaQutY`WB@=~&$8=W@gTivD@c5$B%QqPpSMkqF= zf^9^8OH9GD&mLqs&7HT*$23LP(t{hZB6QptBLmb&n*HpR)FMLI=t@a&0$ zY_igM;{wvk%?GzS$G1L^CUo*~$5tmh1)bpi>2RK4LKiHTrzVHsspfYJ0<36qfRz-r zV^Y6s*}?BY;uT}8#S0*DvHWqzb53ZtfNVyYvy6>{9`C($_m5asVY%QkGr;gIfefX^ zpC4ewne8U!MSmWC5_~4F zC74mTCHO3SJN)P2XTpDOX-jbYGHfXge9!kEeZIA^@@ffRNeOKCtw>wJV87f;+ILZY z(@HM++$-3;5X72SFh>aT0lGmz0bkS#RuF<5fTn~XJD~9)C=t+gA; zb-nV97w@us64U>7<+@4ho>-S`a(2f!t;N4fHI*-1H#{h}eTRqHoO6nQljv2!*S_Jq8-{;LbzY2;Nb!Bj_>h2sS0} z2>LQHmU?wZaH)4k@Zqwmz!WcF{TzyyR)DL^evozKjIxoZsGA>$t3#P zz!=+|fiW@ez?i7WxH}8*eN1p~0rwVgZwc-P7mEE#=Ki3wV1IB4p4;)P!sEsB4LlFx zc@)o+cwWHs6G;08q&r=98%U4n6B~B~$1ZC@ycQlW z8zaXVDJ?v{BWr)~WWnX7H=k%h{1zTRTW;a;D9-zksTX@NgKAIUsx0WX9rGy8Kk| zk>5QP9GfBsOftsnHWp|>E1HoKx0Ow~sr{W`URE4h*=K39+G(7Q9@jT$oc=j@n#y6Hj4LS zs8_JliMP}BMYPu~p2cBKT(d$$K4KDeMeF4oZ<8#_9XnNZq2FXpT58)F;+ zhv~M~n1OiLWbgb|P(MCNIUJ1cDhx)CeKa^;el(c1{RMpeiA}!YJ!v4e_fSvLxL@(_ z53!sZJ6^q58jF0ye_zeVh6lBLJOaP_{+tcPLEEAydty!9z6YZny$|?a!CW8ZFF9aI zu%JiC=21?kmp{-^tyI98siTh!C0ZNX-1$cdmY({-2YV8yi*!%4LyNJm`di!BfGOjAcRFJ(;c_^i;;&o5-e*fA6UCdDL_% zFYiA`5>V>1{Ll7C z)(#g*z0beFxmDwJ#*f<1+rbwv{rm;AiL3-AMOl9iBPvsJJo>SRp7N{hIQ*tK|KY*l zBN^V3doRs+LE2i{{YWN_J6Pb3jc7fXNBjLfuk?PFPYeUWW<@vF3#O3?J8q{5VIdKXbk5F<1Kb=3M52u7#`xF{vr+|!C~YTIHX5#u!lIPzlcL7IAmUd!?*|zxgief zFXAu_9Hw2CgQ*~KvaL7ICiUh?_g-rHQI7K7d9y5kk8180FMZ{QE4$-xdf_b}%b}7= zP^heqw3a&WEZNv?lB^h=4%>(_m?L#PsQe0cZc%J0MwbgQHhtzr(pcMtZeJ7-5`fBdcAhcR|L z`&Q(XEYQMkn{{RP9X5>CO&2McVq=rF&jjph)?qt#_ZJJSV(M zx!t1F`&TG$otM&T&q-T1pF4Jpbd=vYPWi)!$aUhb7oYAinYJLeWZ$F#Q?G>Cqa<@u zzb}xe<(6`~yVaK`@w|fG@^p`6ne8X7kycsuy_YI~wA4QbZSi-|Y0{4#R>t~oRD6Zw ztOq`9;PVvQKZGv?Td_}%6Pr{f-cF{P8MmaRXq0!Blc+BF={Em*QEWMS^9Qq#0ogi;k}Zi*CV+0tt|~rO(eF7RZiQhqU!SJE z8p09&P65Ct_+A6N(*VD0fPW2`%7peS(K(3s4fsDB;ERBXen09Eor{Rq6)2@o-%k>b zVA@|pL39scGIkKr{rBfu0y_W>qj2hm#^{2IdO+(VBBzkxcK z42^tB#F`_1xOq0@2%cnsiwy8e1Nb)%ZiuP);Ayy-_5TDE+s?h za%0Y zs$7;NBX8l2>0wI#wKuy`jWGD?uYx z$&B80w0|DkbeMIjw!a(9X-^V|(HN~Ts&?{$pG|%qV@~oxNAEgQ0ruUK4`aKIe573l zKRqA&;mJq3Tz+gUf+);-J>ru;o3V1uZy^5$b^u=sv-LEgyHa)eQ!D16=vSzogJ{E*RP2k^@h3I> z7jBBC{fnqh3`NTws#AnR)!;4ze~C_X(~NY4BZua}>-p8_s83-`7u7H1#Baqwhw9Dd zqMs?8pmQQ17LP+i{3=uZP5Vbtjrm?WYJYA#`gEJXi~1SD34Rp_(DSpwF4z%9{3v;% zBYxDk(etBk$51~+IKl7h2+;G(1wVS%6F&+U{PyFwo?jEXd(=-6PVjpi0eXIO!EZjo zh#&P31-~}@*7FP4RJ(#;?gYPA0O|R)fFI?L_zlA&_ z^NHh4#Pm4RfLjRMs^o8j?=Zzwm^j7Mn`au;F4b%cx}2K>@fxR}zU>MOH*Z3iRMYA= z6%v#BM2iImO(9n~Gl?{TV~y0E3mXL;DYex&L| zdRa81^x4O^|7hceO%G%olm?=DefdATn2*zf_U1*u6-?an9_~dl z?U1(|!+smHwMk*y6P2fW^RNrnl{xw7so>n?ijt=HutWDrTj7h&iyboJ-RJys$5WW+ zH96h`hi8Iq#eY5cPw>XjpK z<3=`>cAK$z^>||XX^bgXYxbt#(#o&+(^1AOu+w)mI5w|1kSIR`KYD%1dN12P zx`vgGrajQ#l5cXo7<76eK_le#jtUGQ&R>14(O-X?O4!sZ~3>PE+;AR zd`59UR&v(JgqJ?U+}FW!z`bln=h5E8gxk-K>)-gdak)`gy)vJ% z+SsnB0aNnt9Y^`9)kR;-nV4OVlsGPY1Vx6nsLR{-VCMolTd19PD(G= zA47R*6ggY&kJ_ElANx0}gXD(97idOTZjat&b!GO)Uhuk}yDCM8|MylFz*l~-1IeqH zdO!QZuX_{a<^A%-zjl~<6XbM+#YaioF$q(q>V~7(eK=|!?UOYP#PHd2%oxubh52&6|8SNxk><_SIrC(vm@`j~mZn)< zV>=E6HYT$@8txyP|qbww80Kn@+W~SM$=|j=GV9`PGf69jL!v(ntHbGG`Zd zj+x(`0Sl0U`Y@{<7ocG6^cePLb&g32t(_jD9aZ_XQxdyWQ8N#oi{7=?1s(QUT_gOa zp7*`U{%}aKe;?M6eFbMYX0G3|08ldajVIT`j|sIo*SRF6!#`R%>HqArapHa=_1L*~ z2hkimpMbDCwVB$II+|;!wy(lSw$WaJn~!ShFf6XA&IsL~WQe~&#T#aPEAGOO^-gW> z*G-o&<$y!zjwAz*F_CKbY+4{8F9#N{7&)>!m2 zpbEIb-{e6Thu;^((HLz+(i^|Qqux_`hC1%tQEw>h=x!O}7k0XiB?ABcIax6pQd1cG zjUJ;s#yMFVA0tp6X%2wCaX(UJ#s5UYD7E3QwybEz$seizJ(TObJt8mVAfi zFM^MKq5Rw*`O7uX{>w(v)QzX|5;aHP z_eXH3&!3^uiN3v&j(CupgonmoSEHkPV@#LomvEw;(0H3_twTdx0zmx5*pBE3UVul> z&jpz3sBnT`2?F%|J_SIs5I@p^;8%&?dVYHWQ=Jk{@H391UjqTvb>RfvcM(ARFg?cI z<4Eic?KgQuNBAFWzGzn-)X|~FZEc{_kJ~-~oxV(n7di4x{FnM?IF#i>pxVAKKW^Ir zy?jL%Pw+F2+j3#T+J~T)_Zx-AZFI*zn;dwl_&t_0rSn1ONaaXt zyi%wFl{I@G0!s+CN6!v*j{>-0>9O zv&C}aJ2#BZef!m3v+1Oz%z4{?F56x*l{!y?d*(P@xpM zCfx78$(gROoWFL&LB?|DE}YK#V|zNuTkyJnl=2&YR4;R$@=sRG1t%>J(pvdH(Ysq$ z4gU+?rMgB8#`NtI(MIVzXY_6~tUoRHhSxei*Xx9TwnR5os%P}~-~5gH03biL-~VYF zKh#_)tr-X>Y~un1P_VFhwE%{)d1au{+q^%3?pkaf-L1U^ap^rl52c%k2Udglh1}Di zBV7q6L^ayH6QHXUMDPXO^w~4CSt`}L>iOjS+1jn$SD&l0se%r&c?I0&-I_|4?twJe zy2ksu&)vPR=XZ@i=s9=yF>dobXVL!WN?kj!XYU@i5B6>mM&M4^yDGAGx8Tg!%A!;9 zhHr8k2CL?ky{~d>b1SThZ)TF~B*w2ekt3VmdxTrEFQ>if|7zO84tWF1Ean!{S ziaXU#GCb;h?$AqTUiWPd*}ZW}JnY`Tj{d`GYZsf+*FKlqwO_!log%wd@_+p1nSZuC z=2vauI{*LLHno!CUT$4&r8<9k+h#{)9@4gjR(JnS(-z|M_gl9Ao9r6tO*ov*%`z7eE#ja7u?po~H7SPchW5NkhjdqRpUsMPp_=2v2FJ@L@Yd}Rq z4Zg^7HM=&mV1{N0uWr{aVSNQIWY@04*G+M!z3FE(2j5C(NNq~&x+Uos4@py&ovHAA zlC$n?bkBV$X6(X?Qnm+A+u7vB9p>KLXmzhJ%P#56S8xwKKSjc_ztAa8l(6jCo#wus zcL}-qE9q=4 zTeb(MbbVR0PyCl@yPZ4Vvy{dzh@H=-rMBL{)A^1+yrzKDudQ~jGW8v{1HWmUlX&=t zedd3y!95jx?IL>|zL|X*vVA*f$@_Lty_LP~Xiezr^$EE3JF(zYuw7E=V+h9Ay9tuB zF{5rD27Zah)l;|ifuFLE{gkZuZZ+*Ujn=;T&~ht%VWENNTs`xbXd1@lc>HKiD8&>* zig8+s;#`}o>FlR$Hu5OaYKK(n`Q9zZD)CK-L{piSzDvOC2fq7YIhc)aQRi!^$mydZ zQjyaWI@K=^h`dFopL>_MsPShiANa4sxnThlFSC#%s)O$(B}ve4I7~fivsc2s>Re-3 z9isIar>D$1`|!^)%=|kQBrRbZjuKD$6_tJj^UKvyKg$pybZ5G00{tyf?@ceRsN)lB z*au>-u-CcoHrz^nx5r(l?&0*}((?M|Mte=8y|Jl=rr_Ll)ikGvyVEP(+V$zxm?7lk z?$Gub`x?IAlcwrEPns2xf=sUp?;#RbwO>Y(3ha)k!K~$#B*DH9`&2u2zIdDLd?OPp z0=%dm*-^oetol3J=h(&k=9=)W@{~W!Y5t2gsVVXp8IND_$?uitSI{>%x~p>RbYFe_ zD!Z39Qt|oFFBO$7S+HatZnMV}D3!=6Z^arn2%6k>_qr;#ySkApcTL5*nl;U9?3f6x zzZ*FvUAyhLnx12;n{iY9DzRZm6%S=}cfEHtbn5lGtD0)pPqWXjxzmH9$G#ssBD(KF zIp6Ii1?cnU#YK4#v(df=)R4BSs@aP>_O&`ut=?mga;Wy!5Ccu+jScRqnpIQ=q?>8h zrL|C(n+mkCrW$j))$S@VZse5*Io0J7l2q3;V%AyUhRDn`YjHDjQP*T&4DPsYztOt5 zV%;?R(nX6o>SbeSOC!f$`|F!XF)TvIpAUUU~WC(~@UeQ5=>&uQ#SAqy`V9rZu< zjB@zBsalTkK4hBh9lQ@Yny;bLyEYqtOc1F@_UV%RVW*#Qrhd^V z1AbJP6u_+bOQ-2gvrfR7vC69)KC2AJ-@6^`T^VSwrWcM7uONyeJT4FC$gVvFuJdZpUi%%!Pp82D24SI_|d$L?hb2g)Ko7p z*RJl2SPSEe6|~on_OFF^)rNMH;^t6Ximt6^@KBGtYPF6KJ5)o98R1orP!Lu=sv6eo z;tL@{8+cvdpbOM5h#(n$0k~?5UG7yC&9zOD;;TuLU_)(+Mb^bF+hIa=$05HRRt=b-E}drMBHMoG>;O4wWO3b^SX&ys??E%iWC>vkvcC z+5;ZWp8=uHi=|Ic@2{i%ve;tE_sc5eXRB*O6ke%?*JdBw1-j_`KW2y3! zPkY43CtGg98dR$0kJWs;=F|7?34aJt)3FbiEDr5!wUa*$Q`_WYP|4-Oepa&GwD0zI z^1+{Me`wz>78u3?PRDux=Y#mvPAL9(@)4if1;ryi6pz;I(!o#ArxSjrwyzlRzCbYI zEhQhsUnL*<;(CC;j$rU>flvE=?lXkoSGR{^WT?!cjkmgdhSmxr zRuhNHlaX>LWrk?}{)>(O|C+x+Ri`EzF&|EKRX9|6?og#8)(ZE7$bzsmJVZke!AtPy z`8fbnUrso|Zy^F`&e@?Mo(Df_E5t7vkKnfkzxDh)06Yj2PVlQifS%usVc3(8FycpY z34Sg3t>@Pcz>YBC1V0}F^!#eUFA-tHk8FeB_dWd9^TR<(-5v>*4kTjoK z{7Bz|-|P6T=QkHN;unuc@aw^EJwG}>KsHb~!H*_1^!z>r zKhhiVqkIc~#y!Em1wX+9WTD?Pkf=W0F3^#!6;8y-1Cc&mGlZvJAf*el8A^AV#t$Z) zyRn!gY}9^}NBk(gdIQ}S&^R3A*DN9hDWiS*V;t&0ErUc#u?s zN`-rCK{rDH@PX!zd|fi^L(m;SL~8$deeisczDXwt4!Iqubn&`~f}e5TaxC=sjzA)| z_&Gg4?*{#X%+=;CXIy`Sw%PdqXY$25i8Kg}h$PfpnRs=y3q#Hy!QPTd^w!13~Plg_%0@0+q z-^~EIf|CDlpL=hm2+;ldy>G4W`(CYe>zuRqIs44}%zOXqean|A!2$c0{c(f9;4sB` z3Y_nYi$?j3aZx@oF5PDs8CoX~&FrjSIP(57Z_-eYoFbOzlogCS1_yl$`VD^q61&^p(!oMD`vw+s{w zBm4PW7k{7fk$0rc6!%fVqqT$UQb7Gots07`edLX{$<1mk)~7aG$`yk4XtMJ&!K zDR{qGcu3g!Wq0Y$;E}97ubgfl_mf_Nw#^DDMoO#^B{yG}CR~Ri)x+ z)?NxZB}T|YQZ)k_9O)NbE@nHM;V2MB;ZC5KlhdH2TN;%6tz}BKlgY1@l_^so1MES3 z4>|9UjUr@xmY>RKP#(5s%5q|Z@|d+jc>)r1U$J)EjH4i5%21}{Ss-@?-%JaW3rfvW z*a*|XFe%JdraVCRs(kTvmGU-gP+o5u=W#=( z4Ba=i9m67XzMO2ffy!-ia%dUKAXDyVm?&HNmw`qZXh0I4kSW`MTLav1 z16q!3#%ue~kTPXcQ<<_JQ0RxWmnqftt0CD&d z>&!IBiA7!QhyUI6Wy(JIxx>oj?$R0QUrm1se#cm{Y||%xwag6rL4@(t=gcTm5-lXh z@bwZaph@+{HD!tubul1xxNRy*+--Rzy(tf^%AMJHb3ubrkDAz;w_Ry~jJUT9nVqi| zY*+qZ$wUo)Xl+n@c$RSg#0EvN?6d7}HS{S{66zb2AK>?e7jgR;Df@>-N{2Mv`Y4o} z(C4qpJs*`Re`gJfY;m;*xkQ_3RD<#Yr;y0%v5I)5!~nr$6@n`$r%(N9Z>S;36y#!7Cj2N|sWf_w ztHmRtcl9(@|H&xU6zvUljb#GH3}an)(RYFn9Q0SEp`k&U9F89RQ6^*=;3jz}da(_? z7~gb!&*S?!z9M|3Hb~J%DLf7T&*00)w;()|xA5g3T|paIfz~%UG|VobChix?l$BpW zK36z;+eheGHbXU&6HBU;weVRv-{j{IuY@z%T5@e)Tm$&uOQN4eUf>p*!%i)rPm`xUi zZo4oX{YpTO2sfh5?M^_?!uKA&5Ac1MP?wG#mJ2@aFpWY#Lwx)2?Z@{ke7_bN6leH$ zWo!b&JAnOYYh%Kxw`4iW(PP%mZ&2>B)u3IDOMrao@SE!D9bQL+GSMb%L@!8~VMWha z0PV;T@1R$>h3!ge0`;@EAZ4&2++y3VdtXv&s}7C@Q&S$|a#pF-4%?Br-X; zILKiHE*SRaV(M2P7QZ}cHDdYJ_Hu@4V}lZ!*r5Eywi0wg!wqf2-Dv#@4a(mVwqD4O zScX)c58vAU$n@W%ifkaFTwr^=t0Fp?2AAz3}Gw@ z&u2W3{6>N1O*U8i?4<3=-~LSWz!2Pat3EIW!gqj)T^M+#DJs#o^4c=zyAWX< zWiNKgRmHFuPA^W*NJ)23oSrM#ZMjW3rZvPt{(SCUq{eI;lR-S{TWbPbXA#e7E0yr? z?c0^FEyS5d0s9y5M{%9w>4q-V}K9sM$>C88WWXs3YRgO;_nun$UqVglNyt=>_4J_I>KTX6h% zp4AnB#}KbF|7hm?syyDnz;i^MUu_!bVZ^hXm`M}AB0e>BP^FQ06>%;WQ{T^fugb4D z{|gHXBmPBv4D+3C!$S}4WJx9q@jK#;R@-#B$bhjmy!o8UW7Y^y%fW(ZRp%kE2;e&5 zr>J}j7o+9%M;tzy!AxL8ch`@HXl@}}iK9J-@>S;;m|-xN{GQin zvsh_{F)JPO3uqWCL9bbEVW`tGw7Aub<`dmrXAaSfD9j;)JIZ%OyTEM=z-9400bhAe z%#ZNRbPhrLoMOTJ3g1H(vlNeW%V!Q*9GMySPegl9$UR# z=zHPlbtsRMcn4CZ^iK$OG&GN$6n*~giQ_Vwrf1pA)~}XLpOM*FHvcI*xLp>;e|%1s zy9_e}t8rbvYzQ@;r4cngJ1;WV}85F1t0d3?{vj9->+%@x<}l2)8`Lv9(B zY}O&Ng`cBQj%JYh?6JG+uOH;gpWrPW_%lkk46_E5G0Jk8B?{@s`Wqt$&ES$dN4|G2 zdb0J9I4Zew-5$|y8nv}hXdGg%JTKTyYwkU(w;`-kj2Jul*caH&s2%aPrj4mbg!zod zSj?hRND{7olD)jf#Lt}Re3+zGt0%?dr8r5DoH$9BQ-GmUV+MQi|E@cl`Wo{KES~5r z%nq{wpZe?l{=&rZfATL1RT({`KS%wk_zGII^PeWehZd5Wo9N%XL`G32YyXTH7 zwj8w+0z4$Z?%(Ly3kq{bJ9}PZXxon0Jw~`@=btt(|4YXXj{}2QAjZvIrK1?Ryw1*Ue<3idy3Bj=-)pZCQ zv|kcK8!4j~ES|eCcRI|4g{nDAx6z$jHkvfOvC-2PEMS5AuSmjmOX_<2uRVURKBc&U z-+xwS|KkgB|8MQ5-iD&4HjHf@{-Zr0>ficL@2(F55a^E_)b914?k@`f>cTD!QHDEW zR@lia^)T{BUEYf?kluZ05H7&_pFrkA5$MUSNMC9DN7y zp-C3N;6D-jhLmUhM}Fu>CYr1NHx~XX00de8KA#14N}Hd>!Tu@svkt-3e>%P1`(6#m z$EUg{n7Y4QeqV0}&JF)-_w^E!67EnpzW(*T*U*P}g#P<_cr4FyG7PCCH}ThA=CDWu zrJr*5fx5oZh%~aPT+%-*eJ;+^R>l9JNZoSYJzNI~Q z{*F4ab*&$^$hPIewp@Z8^TU3yZE1U3AXsFi9(HNMmbL*LHpLI?22A9zwSHJp%9b`S zhaL07x&g~k={afwd@5$okT2Cc6k9mCDjGPi4%<)sQxVd&pB{z>vIm*q*yeO$)0`6{4AxV-F z?y0%*7hc+Y*SH^jgI>S#?dk`*3z9Z;Tbe%UmiPa5ZiN!>ieIuHTAA{fWX|7)m#q%T zj(5A6Eywz^VdvAfD6GEU{GmrbY+W1nloWgF!`3Nb(WhQZ8+fXyHm~+gcle)LTtV=^ z*4^)vaL?IVL(@99`P5)3r#3Im5P{_7rkz!%Zz6;)D z+~-WpIz5eXJ7ji#e~iUVY|Omayit_s2>OwtCKlO>DO$roL)62(W_>n@aJZRRlhfZbW^OtJ^jz3E!<~{aItX} z>A-N7LI&^Vc|OHyTcfOY3h{S9Q$cLw8fALcZ#z;=$7a2izOC(2&@bGBPWqHFx$|*5 z_vTY;lo#sPC_lGAvPHof?BS-ggxVnG-~LfYdicj3eGHHnYR3!o3;Qoz3JX8Q9F81g z(>i|VlI&}>+m|!h#^yCjZr0z;ziZy*_R9T9ia_Et_BviKxDA)M{D5!0DADI3$^KcN zc8_&0R@1?k#Wv{UfkvC~v=y~Nt0^TMI&nzX4Rn8`dF3Ns;eyvy-#0dO!7Nkgw#%jn zl11>ocT=sm7TR#y28BW&4u6qOa{4WoN@Ve}3EEjAJO=0x;(A{)#a0V+2niQh3be04 ziiC_aB{JhWpxaPxw|Cpuy!?yyz(WWoy&3TU);>-0AkV2+Y-9;zewC z3QaLmKWR2wi4u`SNK}ZId~D>yXPA{WW!J1k&1Y-6M%)IjYXtSVVV}qRQy*A%Q@AwE zEVK!RON~}YKNUwJjfs|VM{60@NJG)X7$H^B6hmuGtZ)T(Z+NigIk&x@a>fS0>M?)IRbMO`HulSzjEEs&I))3FyPkC1_ z?z?zr%+fg4e%gDa%_~+L`uS9yQ?I+@i0YWpr_|O4RX8V84+*1h_X&5ppq0e4LKt6N zn|ZOWaIKrQ@9~N&X@BnxoYs^l5@e&dbcu*K_IoRNpZO{J43kT}FZgS^;4q8(58o!_ z>S*)~$S$f8Z|#V2j?&!0T9JUQOge#mQFu?^IJ<1Jav z;wCqiuAo>~dOu!~d9i-sR$x|oDfZRgRQQ{Ia;rP%n1`oqrPr`>t^59yNFOvYq@PS( zBHqT@%e{srB67c6OYH{lCbw|1%Dw%RP09*LX{D0uJZY{}9(7hKFJx6JKX+B4Pp7t+ zhPqosdqYS1P3Y4iG`=N~J}MUff^Q%CwFNDu;P6^b@tvbTxr*L&=aG(7cB1+20O(x- z_B#UXNdfi*$r<1pDUA+r2}|MwTq6VQBLeKh0_=_eyG^nMxWc8F0GH7=IKUMhU>_*y zxBO?ag2Mv>FfU`0is6ZUh$c7&lVZ{zTZT4h)+Tv0SImN++b}sK9i!XvEdr|b z0y`jmU6HQt_>#pb^44V)N*dr}aw@PBl;r9Qc#6CU&{RN^0DaI84S%UZ@c^0#=oCM+ z51?*969CQjLj^!n0CfU7*$+(x)CK76fM)rj_W+s<=x9JQ{m?Cd-V5j`Kp*f!PXPK8 zKyL%|ehuoDrx#Z!_W&9X=zV_ZV}OnaG!D>78gzVPg)$DXk$`3Rsr~}_y&KS2K-2x< zJqSM*&=G)6^h3u3Ivmiu0G*&gqv{sf!Wz9caWV0WY0+nDHyWU?^Wt02JNB(~M@k{5 zt~5`zO*8+)_gCnh5fY}EKdb$|u^D{IAm2=*my_8Ic(*3Ve=AY-85uk-uD_|Xaid#k z|Egs2;1#u7N_VYr$jQQrT8QtJ%gVl8@lLVT@19dOZ^d`TIGJGHT0BRWA1s?yJZnYk z3LMHkM!o5V5sAxG=IcGTeB4>xD)3trxaU?<+;NP6-#` z{=xqCIC8D!uBC|sxa$~qbtj746=@@%MQ$&5P2sL?ca9oP#bu9G!>PFJ7`TqKG0&Mk zjnS;Ftz1A|4+_!sebruIn3u66ixl@~_AGRPc>)MO4*Q;sg zB7I_eY5YoL@i(8rRX+L4(pt>s1Ua+3&m{2Nj`1aCnBMlmFR>AyeZ%71veir4c8-*0 zyU#bzdT(Zk?Uuf5&Rw0|XE5`d>JAR|*5=JSwH%V>+k+uLM}T(2{>Gaxl|a8F!#pP( zuiAs8dU+Slo`6M^ngf`b$w)MhSiLZ>yW;jpn zWGRAtq3p<{Y>qqO$E9?#$s|MQFmmi_|3d)W3-~hcuQ~kx1i)GD3%(i-{fMARuZ*9@ zQaZi4`M!5EsI5P~Y?~j-=FjlG1!zk9s^T7~cmAWkEgYI&;)lv!;Q?PYhZ@Uzpf17V zdzC}`2SBaDSYHK)z8wIy2%~(fIQ04Q9tvi`?knd|%S$~_qi~DQ%b^bhP%sFgz83&B zo>G0%oq^{vYFj;F?5L0+w3!W~SAN_P9I;w4TG?np9$%K9zJRSluDeQg+G=St`9R66 ziPNAP#?^k=kJC##cOt<%1K?_DPvh{v2f(ScALh{J04SAq7Kd)_$!RB*_9Q@4+Q0Pb zsX@)4w9`2Bm7Y4_rS0O-tRA{DmG(FeeJlVi z;%@N`<jAd_EoVc9f7}t4fD;sFqBOE(7S5yZu|TgM2JCR-Amb`GX|_1dd!o?v zi;o4Sa%JKcY@|#6qVz-t__FKY2tX* zM0FHmvKLyEXtntcIAr=Db^N2%<~tgPWT&&;#3Qpc9{Hz|U!d<}hHMf#QgIecd~$X< zbF5ZOPCN^d3rhBbmoj>`;BF?NAN4!4B1z z+78tKz7sX&njNYywH>MfH`t;2Qrn@5g#5OY_Q+z~9<n%vw^QQu+hn#i*^NJB)d9vF0g_vMA~&0Z7zw?*}6NT zDRA8UO~oqH_4dZHp^=kxIjR~t)m1VyW;xkt(#W|NEz2d3Tqcajn|FFSvqDQ!dxS9v z?>t#bpU~{H#2bdhx!P?dL+qSG#}{?}eYb$N8SETv8)65)7#?)laB*)fM%o-h>&+5t z4RW!*JzU~uX_PPANV+;(cK4N{jK9nHV$xn{a>8hAh?e%Yv3%3ft!)W~TQ8hV%o)c7 zK%OK>4Inez&T(G0x6PYb*V(k&&{pJ5VTKE@8?4|ahKt{3d!oL?xplH-qx<)b>b(P# zw0Yif<#Dfi>Zs1^NK@{wl?^$LnqMn>oNquXVvMvwxyAbN1#jV++TvOfdm$vDkF?W< zduTUBOzDx!7wI|v-7QOX*?fb7yo36tmb!nfgw)@=WV>=xeI~Sp(T;~`>$Yw!-uj$% z;kLZ5i=TSV_S_o^v#kFfGtrR?d@RoL=S552Py~xTYTLjyip#oIapg>jdISC0Tb%du zYiaD%wh}gc+dOF2%qhc6{}^taP%g@_LO5Tp=H%^X*zFlU-*-dBEh|^xjrAmtm>S7n zSVNkR_QLv2_ua>SeYvCS*B89`v6!WJA-vNu>#LH-29NX&U%GkamVqUGD#5=oAGXsh zXrmWO_hRj~ zfori!$xG`xIsCa;$w2ociv?Sl^f}f68~#+?%;G<4A6)uLYmggyGd%;1gF?eGa~ovD zu84`|Zy(7H@0nVazZG>>?2eW0H2$G!A4dHR(7;u6G4x%1tVQ}$k~_D(OXF(tMbo?Y z^6@_zKE`Xd$anA7Yy;ngj?=Rxn2o7a5_jpdHI>R|yGDbzCSQ!-bsL|vC0|V36{p$m z-ZetACGVPoeOS+-eV4CvU}rNwv!x`W=;Gq{O5AmwFTMv#c?+9g#r}*CI@nKaz3Y3}Y9+`?ZPayMr~k9Fm?b1o zZ*Si%V5J3~!Gfg0*6yxv4l%69?<&!G$KRUwUH+k6aI6tNKcDm5mginPKi9p4k7TUl zO!@|Puo(`D|H5q6H%-DvE>>8JOrUmKX07Kd@HV@Pqf`=v?YhK=Bwx0Ei+ST-pH zXL2;BiBxAhDVXKt`e!@w2XM=0X+|_=J6My)?SSh=xOUW~W(hpPDuC-8J;@{ZNykUY9iVQ z?S&-L8I0OSYS2bj3{uZvXs%mBZ6t|wbx(6|s2vj8-4%6!YA^^jc*`V+MbqwV>UJV$TTk$Fu*eEj6V_8gS;*QCITU^uKccvIDF9XtnLo z?tKov`S>PNtBRJsoD_++W5#~Y4!BOhwWH1jE=;bY+=Vrb=EiCza$9@wf*p0NwZacu zmF#^$0gBT(5G|=W67pB`Xqb1o*5+xLJ|)DmQ&ujoh^4kj^~T{$sJ%8Qj0VBg}fG zE3EECtuK2^f1dGk-j{b^FQ%_rAGpo$osLgkxu7ril@_EAM12_e%B8O~AFdsB+5Bw1 zuQZRl46WG)TG#YN|IyFZ4XtbZF#T-Z(0bGl)6dpP$G09!9a{>1XSp2iSI`8C2}2P#*}HNPQW;6CdT~n#4D`c{K49HzVBC zmw|Ub%FQ+IH@SJ#eTthA+0>VT&wiAfYqHDqWf%-_k_{9!yBG&c}T5${v zjp$!Z{5%4)NsriS@L;dN)7^FG0CO16-jYE*w7YBUfy&w|T``9-yOV-iZ^mxhpwNg* z%fRz*#yqHAzOD33ZFkq-4~2r~M>T>=I0f(uS5hW+5wCQ8b@)d8lXyJwdKx2&Eu*9 zTKpUOA5}-Ydcg>B>hRI@qXOO0sBdpAVGdsgjU3e4{s7yKI^59Ut}nQY-_YRhE&dUK z-_X!t`iQ`9XlO8fMBq0xG?+djfL^3Zf6E1ZL;yWMOdkKUxdj(3r^0wMi?vc`WIV+>G?}M{5DzGm)EXJuA6+%=1TX zMo#IE)&l&LiQHT}WhFO{P5C1?BlDiXNIs?%I^WGMxw0gh){SmUsF*q=bOUcCR144Y zx;S*0pTj?Yr7QCAf=LTe8I5(`qU@w7OQrtpV|}f!4XM z$V=Xi7^4BRx0`X^Z)nA(@R6)F(o=WQ_MVUw(|qmft-H&H^$7J-x`z{Z|Hi-Xz*np( z7}2AP|8$+1Zko0-;x4g8k7skZH47e&x-Nk26ud|w| zN3sv;|NRd~ucY-Ey>49ZHGoUHKV-HmU6v2b`0t0g`<1SXoSW|akk+_kvCDC#Gv>Xg zGoGGwrR!E`4tM~){vB`LyhPmSz6;^c{>CNG;irIJ^!}kAxMZ1J2`k_l0N1<{-4*nK zXE5;$R;==jh|tK?+-4Q_i8orJ5oi!#mS&Z=Z1%=L<2VCvyV>)Hv#P(}!XNP)y0vnp z>&rUNL`y1zrugm>%o=FlGdnq50LQPpXx@!^vSd7$gM4i{Ncs8|&)3gBq!Gk0;!4+N z9}I~rx>&Pg2zIuN&MRHN_>BdAR~?MP|MG)Y{Ffi(rG+z!rJZ|rCE{k&rUM9FXt*5I zwyL%eI^vm!S}2X1gFk1zbg!Rf2qI9tPc;%juj1$TW`(ax^{ zr-`_qQ{^2nNw7bDE@ILf?q!{fC3oGolYBkL-thY>>P+2<+d$9L$abY`?gz0*+g%5p z_#bnS)=N8LP8(@oQ16r7-9MJ<8>sFrPzT4%it;y{AuH=~8|(#~(7` zI`l{GnTZnl`}wiRU&DdC2Qsf&o%Zg-+Uk_=ZkE*I8Y*}SaXI8mioCQ;<0&_Lh^J65 z4!^n=JG(nye^*R><1E$MYTgG@4!NlQ4?KZ0P}crd@xJJ346_+ma_7r?HeqkII@;Cg zbxmW*on?ME%OdxSez%vq7y8{ShwyX!ZZCJw@Vi+7;UD+Az1;nv?v}lb@b~%MF78hC zyRF=PkKb+K?lFG1nY(ZEyN%pE)bBQM_h7$UV02=ad@*v5;Iy3A+Z468sSNvs?Y9<} zs-=S6IdgctEC1!cgub^%O(7`ez%vqoBVDzpYSLBZZCKHbT>}m z2>+?y?c(lwzuU^)2mEdecklJP&D_1i?>2JxX209O-5dOFK~PKOmEA(Z%jeXR*yQag ziIuuftt3kPZnY#{@VnKLDD=D4lK7e5t(L@ezgsPd$NX-!B(in4RuUP0w^|ZzzgsPd zaelX25{Z7dS`u-7w^|Z*zgsPdXun%8iExxe|8o;{RXU=E>AO8@%lig{&()Gz<@Eg8*U|2o4?zqx5 z<%8^lEdI*HCwE@y+6OGV6Fw4t?D`dE11`Ep3eKJKfd%`X{LHKH%2}LG*aXaamh&_uwVko1SnlmvT~E#D3#(auXi4k4|(5L zLcLp7OkpdaXMUntl$QpZ<)vnGonYIxVh!DZw3BWgGo8Dct83ZqH%sO^?+CiX*5<8r zyX61$PDsN&(_9*Fy|**@rQ%nWAa9itvpM>*`O=e@R$j_%`Sekuf!hhi!Iwk2GSkv= zwmOAI(ZqE2iaHALd!hFif7<#fbRV(wyB6K=nFdc+$FIHKc1N+f?WMk%EvrY33FSU=m%g=ho#M9IQVqB>REc|8NvUUQOWh$pyPRwg z@n$sAQ;wTmo9Bga|Lc?|EbEjf+1D#wErR1nM|z}?wALN&6UJo=^s@Qmjv$9kE)oo$ zfe816rCfQ^x_RDU9+vW9Y`aOMo7|ye%J47J=|?8ubsMC0xY1Rv+-r5nB5qsX>?nuZ ztmQ&zUZ+Ubk2_L@jqYFirZG{zSG3Fb8Ypd&b0e^6)_tut3RsO;mv0((GpsVq_!5STX5$+LUp%okRn=1^WOB9U_)Qn|8#zm z^uc*{`@?iTk(eptxkuB}H3u;|7DcDG82V)64%md?3ylYHTM`<>`t-SM?$~;sT8G7s zlw+6pO1ID))HX~Ahr17V2esWP*x-uhuJ-fPV$fC?wV7_m5trItes^A|_9mUOblV3h z0!lSlq+9=@r>`P7E6*n_-V|MtHYGK=>(xEx)?h<*G;4CF?rSxRw@8;xuR4EFI*5C; zbg$ma?|@m_=7$-40+(F7ZQe1(nXqx?V!ULu-byLVsu_v%MX~{MP_>ba+kCmebPjZ` z<{w6B6*vd^HnxTG78A6q6fFkrCwM)~!%DPMSZm(%I<&p9wTG3cy{{{`Zhrmy81FJ* zDICA`?qu;vf97eaJg8Wl&+OZ|a-A|ZLR{mD8;iTiSkL{id6uE#cfntDymFhX?Y?4W zGhGNST_F z?}ViN$`?s=D{8G<^x5ZCF5l=LaV&n;rd!i|g-J{vR=i*NRKsTgAF+tVNj_t1hLH1% z8YR_g#(M2&G{d|Kv)K-7aSiS;uBpZOVmr;#BSL?v{K1jfM6Dwdw}6>ErC86QtKue{M9kh6p>Bf?HUGR&!`s z+Hrch@M6GCu%YVv+bn{!7`x7%lBU%Mm7x0i=$tfH z$2}#?hFfrkG9})!5$D1?OObELP4QS?SB6<$|9&{Lr6P6p-;}e~P@&TOY2DV=P=lfR z(>nOW{q@_NRmxE3ab-sRYGs7;HE8yGU$IwgSF9Cu6Z?3vfdBW4g=%^U!s3g%X1+(| zZn{uk_^C9iX}{u5+>myF^Q^4INI5(#xN$=;Yx{+Fzw&?|^SFlD2+T5I9`j>naZFHS zb@0;T`<16Oxbu#6?w|YCDXARW2<+!4sJB0PX~p-SH}6;OOw3D5Ir&kCOW1whF)tde zC%JRrdtc98L#O0%MO~5aG2Aj3W}U{{K;LF&KX=~ovbYIva(ssBU1+V<2ieHv&Ug1j z@iwdW%I)WOtvb>nB#H5VYcy0x!WWDGhtF|qlyp#uu&8oDAV(wCB_H6MHXR$?{ zrF)9b|5jlYs@(B-gevMhx@R@s;iJ~Myzy9=*vp6f3Tl(@pV#v;zgf2}_S?M8th(akq3%p}+^u7tO(d|A~ zbGf4p?`rb-4n?kG)^5danL0SR^9gXBp;plF{xa&xmJJvW?`gWJu8f~+{{~~iO?A&o z!m!ZGrtc@E{2(|oTVA%f(uAW(8{6n!UI&WKJ!wT3@7NP^F!6k1 zjez$R+oEWc-sSE(iCkGnoEK~#HLDWE!6^lmZ}xB+(SI-$Q+ z%9Gy0IrI1YB!9~(ui#4CaTFs|-kZ%^ik2IMeZ-rriEH`@aVBXj=5N3`g(eq7cI|Xu z#+_m&KZAZsZ%uaMge!ZHz)#!h7R@?(JCtCSs2px<7OJPM*oab?u;=R{x=A;}nsUB; z5mMu%`hF*^Uf${zmTYuCcdU3tC0_bii?huLX*D&!rikdD`M&+&S>QnPSGs5WXpgR1 zIC8<8By3{wM_0Hy!iuBto{TjK2`=*wjRN#iIxh9^5aVycsllYdN1&rp*f_2B4Jn!6EA=+5HdPolyswO@Pl+~d2*n+ksOaUm zS61fL%Xew<2W=wwZhmoN>PE$Etw_Vov|?cs(OR=4JFToP1g~q3)>7%CW~@{L#KGP? zM|rL5T)#&cPi_26Eu;%nKe}Sq%2n>dV^vBVa-8QA#^as~xY#hKu<>)G`$6B3`1?;y zTJd!e?!Z{1CCAHa-9;DQ*iAm{RHn}L=h7CvgBvOp#d{TV{g6w`l_uz7@Ra^}b?lGK_Myzv+Dy{$4}cz-vKs(jFm6O;_6a<)?efp^sJ$ z^(cqBi!Z+S0eV7wBXYKxICcEFsWF1B*cFm`+C}xfrgoG2-IH&FulXFd)s)Ig_sCO8 zhlJ|WEB?{UAQfU5__D!DcY5Ni`hA{n<_c@cZ&|ajDH~;II2zK{op`iXFF(DWemu+S zNM@=q{&e$KFIS>8=e+lI(I%vR7WVOc&B&jnrbf6MJFTLH*)??m2+Il-$+2r+=GZ z)>A<>df+TuLONm%&~`c&Ya4^P|0ULL;%_TMokv+_i<#D5_ZXVy!hT=q4)>*#AEM-3 z&}f@4)9bOw2bR}jJ%smaeXt+XpZ0X`!>X(!^3uz)aJhd6?WmTwg=3FVY#pq{7+_GUb#)^GAf%X?HhhLI-pP%5A7 zbE5Qovkj$Kbn*V($#GS6TUNPjxFHEDYid2`bnZblt>^qFXZ5lfdYXEH4!8JnqVV>E zxl$|KA*ZRVsEjD&(6b)S(FdIqHn^5#rNtd3ENU0JDYy?7dG-U8o}wTHM(UFUauTw0#9qt!frb`~|`+Kjsw{Ri7)a}I1i+Vp4ZcbZOtR8NYk8epcT zm$e|5^aGHVR8px*ni(|xtdL#UJIovscK`uD3*mhpbT<^v&Yv+mZ}!5)P+efvLI)}& zu*N3dW*t3xv^61d?8xiTTL971q?llart5X#tiqWKX64VB0bK#4t@WvA=Rkw&9IlZd zFCUsBND%TkYl8KjpI9Gw67v03%gjXuss*8EE+lJG(rD{L3ueM3%@T-Y9+V_J1v)zY z(MZj8@|XX-HFqvFKP-F}>KOdKsZbW?=UW%dC9Scsb7#-vniF`IIiYLH=>~NCDrl$A zw9dgJcNFHrSx}y_5DE-tK{>+pBBuy?(_ctop=-mRR8AN~pP#*OmZm=eS{zTM=SY8k} z2&)fdO=XZ-Y^=iWdBJys{IIhEuNjD7A7=e zaAg#M4edJN^|anF{B_#F^^$zU@LUPVg6o!(PN!h@qM387Pv@eUsVYNswh`Fz|4G{a zyW#cX{_lqGNwLOUNEI|{7x~Ctkf*7k$uERzk#SH|GEe1(U?nJXza44dc48hkYDYcs z_!(-ilJSQn4J~R_fVpX6-mlhJ6TpsbJ$&FQK`rB~P`^WYfwcJ9b4kmKN^(K&Q!~Ms z=ggivd*K3`HFF+U(K0u`aHih#k;%fDPtRUJydQjXA=IU)3c6pIS(s1J>3RI`#HXoi zSy;GOkB(H!5R*e}dOKE?wjkLv)i#&@fRs7T%7>`?_)sH3;mWgxGi_EbGo9-Yr-a?u zU&`~j7$l&8hV-|f+<7+siGRpC=TteX#NYtVx{#~bDGVs@YZF9Vlk2`onLB+pX_2`P z8Cz&g#Wty3Qgswa?EkL>a4P8`ux7_7&Iy)kY9drn*hD*nL$ zw~ir`bODk7p5MS8D{LYp#$Sym3GzKZ5fav~#`_D3dlI3M<7zx<=;-*#IHa%NzUr|K~hkU12z$dgMmMh`5Ph!U#oiwN>>7Yq-!ehC$d`u zouD7X<<(OgcpP5@_`#R}AH+Mx_j0@8CjXH13;$-WQ=Qz@B}q5ZBA7kyzU&v8dmP7y zvMrkXL$sj?q+B2!zy=qXM|1KAIY8;I!uu zT{i6+K#k+V2S|}&ioU2$yFWz_j3`n_AC2w?F>U^W3}4A34F0PRUm%A7fRiURAC za{4S)52ugl9>{+_AUs+^T47;+p{j=@WFqk0y+%$JF+zvT6 znz>Lj7is2;n(5WdGR<7BnJ;T*g=SW1<_67tO*1!ZCdt;J_;zUKyPCOIGvC+D1DZ)w zjSy7UQn*7P>(Gq~3V{&T2gn5cVKM=~2}4iF!whP2RrrB0n5fUrNSFxw35HrOs}w^j znKYD;IYu-8K=Hu;-^fIJiKg;}`(85PeoS-ob^!Nsa!fo!DdWTIYTU?+O9WKLJ*D-j*58E(`Q&AEu)Q+?oupMPIAtbf!{Ma5os z;OH&&mALD5HHs19n&Bf*1}%BGW)QiK2merV9h^F#TrW-64lUR7#NTNq{bb3{=2ht-Rr_%t+Z{Od`-=A*aud$cECitUqT>t5`{{cW?yriFt z=IQ!R_tyeIU_J_v4-EA@AGCte^Feao0_mMZp?!$B^`B190#AYT>Os#17?n#HKAm1d zFM3r7MDu?Ar_&qNiyi~toCQxrFC3pvFRK^5C=3S5b@F>(FM6{;Z-+(?!=sT3POLP&e!0D|8#o$G=GSW|Dxb;D>ZoSNB-c2o#^H7Ll0vmgwTGvzph^XTxpE` zR`&!`_eWzr1*f?t{Y=vJYG+Lt{Gs8>|I_JF+X?jdEy{WPb^PHt?%4%T&cL$ZzbLQz?mO;uTIu(% zohQpLuli?fHZ$iGOlX95i^!TrWpH>Dm+i}9&8I7skc6O&j}))fBOAnGxUTkf<*yK( z3t5c8F(rFLr9NjG6>kWaq;#GvIY0FI_e|%o3dE@8Fs-7q13NJm&vP2o;5bwTkB~t z*!Q+(4+-(P1W_J>Ys4&MnUcqr!H)oGqg>)BG{CJ@7G|ZjuwirieAZmgskq#yn~74; z#774UvMA6UHTKPL&1JiOsEuorc&8xtx?PglY<_vbI4xNgRi=FJ0NTE zqL5h!pC~vyQnKIga)fD$op_9_=Hm;>Awk5f1dxYW{M#+eVvvgqKnT$P(4Qk zD+eB_4*ILsUMStp{bEe%-dj-K8=SEjLS?Arki$ibwHR(eSs!L_ur*W)jNg$LW9(<5 zBoVT|(dS&Z-jCjD8!8J%^j8-?>WMD&*U1EPL;oDX(T8np)Hgor=kW-8_**AR52bDC zcR%XDMtZ)e{PATG6lFb`U=*Ib-f%>ITD;W z66I>ZzH9T-|KG^ZM_PU&r4RjiBEA;H`C5H@Y$?)`HK3C{>yqP2P%h4YWL5`f%IjIC z+y`<)$8*?*Al7ytw>=754!1?awvgMV8ClzMZi|BLHEug4vbMe4wh6WdZaZUOZD(<# z*z4Q?+wZvT(cq#J1c?qd9w+lmkgM%;NY)!>jX=+J$%jfdFR4-n)aMOMF{iYA5nhTC z{%T3R;A)x7N|lYwy7V(4r6o%!RW@02m+J6pw%HGV&GMBW{yMW3>Hgoa z>lx+L0WFx}ccA4t$rTmsY8%Q+l-oOmAyXBJzl!3KM ziM0~-Y~*|+up=!;1$WD21IhuoI6tl$xOhMAHQ;XZ<6Z}Dlpps7aHIXWH-Wp|kJ}8K z(~sK%T!J6B6}Ut{ZX0k(e%yB8#`tl+1nv$$?k(W%^yA(J?k+!W2XJHkxOafN+mG7` z+&DjO7jWbKxOaiO$B)|$+)wIF}z+16+z9R|}lmkJ|^F$B%m- zxKuxGKX7R|MFH#V2A&@4 zdGaT6e~UDKu>t(NscrG=QGrOMbgN6_;T6A*&=L!1)TRAmL^qd<$p8W6&pq{p#I3vo|Ch=p1Rdq4=5 zq_(yZ=cpaIcT;PL3NE_v<(`^zRLbGJz6WR_jXfd#yllfcj3?F+eAYuF?Db|oI*i6n z2K{SqpuN(VtQEt!IW@vz`%SR-aZ^Dm$97qG4FkSY22bVC@vK)@}B&+s%;s)!58m9@6}` zj$tM964pAV(@O^Lz^WxyN~`^-d18%_d;lw&u~@^zYHOdO=G&!C|9ptr1pQ<_N3Cxc ziyM{Y&QPfoYqpKfjTn#fi;Zo|gPf4y^Xh!!Fj#%#9nE$x;U5c1q?wCFsIp^D3uVHP zmK-q*`j?&PeHMeV{>l1@tU`43@NE%%o8cvch`Dv6@-kile2%va%Q=S>!Sfykw^sQj z(HG@9k*IyptgZkNR}MbsyU@e9Jd0kbRp3szMGBJHbF?zuu2iroEkUe7c{vN39>X(y zSalX053I>&)s<;1Uzu8w%d1zWBne}y7jayFbvj&Ko!+dkPIG8=s?yR|r^~KWo6EG? zRO>5hf{oA7WnObsuBy%q#F~YVErkA`p7zKbv7UzS>`7;@&C6A*Y!-VMK6>S5)H99O zwx1WY_H*Nv_69ynf9T&>dn9)bc&}%+PQB~ec$2hvA4i)G2~Uv?0+sVU6Ny)XABO9^ z5p9PN28>W&JAP+}#?IuU0L;(_!I8bv6j;9BC_&5Qi~=|7sie)}1;PPMo8 zZ@x?Vhrg!nC?I(P}))P})k@A0rg_1rU z$7Sr^U^dk0T>uX1JQjp!iW=XV+lz0dHTLj>>gautmou#oi~nmQh0mJ^F3x8@L?fPmS zT2(h_)wERtc7{SBDQ4h}_JyGHwKunW@L#r8z30VW)ALL*@njzKRQPZMN3D5K{pzY~ede-(SUhu^#o% zC|KX#g|em9sCQAu9@SXqutyyH|b<~)K@4)r;L#&CQ1{eJ0x|U zIa8a-L@@hn!Vd!PCB`T^<6-2gbGyA;z|c`a*ZJGfR3@GVndHsRzfZ zoaB|eCH0-J?xt8o2gRVqMKK4*G@o&4yLiDd=dKgWqf!>ML_g$Isk7KW`b55~2i?QL z=(YGw#7BLR^xoYnt&l1tX3OmSM)xM8!wL%~ z-HNxl<}AZbHeyt0IF;h_qQi+-!68|CY&*R(Z0byi;@XGzULj0a@4~FrD!Z6rXT&i0 zW^6spxG1FAA9D3oAz!7C&JCE|z2-ISgl_L=DYi^G#BAar4ePN(TCf{2gN9d){TbAx z9{01@DJ5Lr0>jQ>ShZ4UH6*3BC(Pu4Fw@Rh?0Mo9Z5A8yXl}O7VK}JgIoAH)rgTql6*DT#4ocKO=OzZUiLg#Dr!<7`wsY~~oq+2BBm?ib+X~a(SLMqLtYi@2L6B-!etDF^)6wv#h|k_~K`^HtWzRKwE3#@lxF?MIc(NnL{--N)ogp;z#?TC3{JyB&BjWPbE?Lu?D^S`Tk7~#1zBnj^({L zC}t#L3gwVb50X#v(b@=&_q?63;rgD8j%)J`pG9^Z9w((pMm=p zZL@|R9GycpE6x&bI9r=jfKBZ?k|4UHn*vf4ZThCJN7VzA(`i6;dktQ^xkKU z;FJV~Ua>QdS$%7)v&J5d#Yx3j7ogA%#7`gm=0~%X{lA~IrR|bolbf|aP+YutRm@Vn zq)sUo7JEms_B)D=vp$*he=;1Yq=jx6^k+STRS;{7F0T0g1xPTcZ=YIRjeZph3^YlO zM^DFkr$YHTw842qG2SJ&dn=R@YlTt_om)$@DwI;_-&$g+P|6D;AoC^2J<1U6Nox_( zDwHBXm*q?`_w$JILrGFg@bIJ2v(0PGg8Wdz^X9qH!SX|QiEfPHv?eQ1E)9$;5b2i4Nxx+stR z;fnXw4eH&3VCvQ2g*Zr`f%Ek;xPQR+8V<{6;QJfS$O~|Y?mhCEz(yqqM%dql{|E8W zy_Y}1&nz6q--Pc&d_K@{AxsqRwY`W>#WLgr zOR&Qzk;cp;9p$%3Er#J#cNO1fImf4d(oueMX-AOwhI;^JhCj7bDNoRl3P!89AaX2CEbw0mUF$41$bOPz(jd8AQ!u zmqpBSprIN(rnT>_A9r||6*5zvz$GRHT~&`+B%bymRylt%cfC4CQ){$%-mXadZFjzBB8 zm8(MeS%JI7WN-X#=)d2lkGGLCe`3Vr%#LO<3Z!!LvTrVcxi8;sZg zka<>St**75`1pc1u?kXXRl3GbV{3RS$-0QVR3<}PF7(mfjLEi_PTH2!=PX-hFfGVvLV z*G`m4A==1%l*z~p+a!{0=j;P)HgE`SUwEmb*HNDWfP z`bmQfRY;wuCCDRc=@Q*>z|XT*DRWtsl5e4LgSkT>a};+69M1svGqkK{&`RrV9!Z7Ov`aO@mHX3;bqV zAw`cQml+^SQ!cHAtY9~!o#t^#r+s%JY#vfF!-*RLh>0^Z>v&8E zXMH0bJHW^-<-$U!73T{4Au+!$(A-k|!5&$WIZUs)K22^QZ*VtSrUmq&pK7&xxAymU9Ku%$r-!L2G1b ziz&zjEyLhbcL+@bB(s#l<@%->l#}j*Y&6#onwBsBU-sSvtg7qW8((K&MumeI1SJPh zgJT%f5R+Co9JYuk1k@O{38O=TfPiDI9&jFG)TA0ssz4ewY6MNIrWT`VYty6!HLcM! zCB{esY414*x*b}+-@EqS=WI5ZwD*4Z|NZZMx*rZ}y~BFfJg@aGmW1%7A8BHPjaNq5 znFX(s*voCWZiQEPq}Ngd`P<})hFf?o6}quU(JNi^EMHJsr&D~A__~I27Av#ajkz_b z*OR-fcz0SQ=4Y|Y;6a_#qupQW|MD@ON@$0aauR`6L9(-m^*ZXme9-RrhvO_3a&(7T znyV>mZsFaman#-VD|WT$oYhZn+^Zqo|F)RTw2y%nXCg!Uz)xnCTip2?g-#fXSEB-j zwN2Fe{KV3;-UE9WZ()6Qs4%1UbW?xfM@@#BABDb!6||v3yL07jG2hR^FoB;9B9lI; z{s;5gy6UJ>U-g(R8U_9SkG|H&>b6lWHw(JA{8Mycyv9cwuJ||VcIqSj@LIvr`TG5Y z#oR2i4Y~}M&UenFnDHpuhRABX{2h_S?ap~0`5pBRPA5g^!;N^aT-Xdm2j44^3Qo)V;wQTCHhbhk0te z-?**V?V!_X(!LdQp2IU@HGq8YdJ=C6<^BU!MjyOCpyYl?m)vKOephL|6}gR4rap(~ z!*kmpTy*B3(wfmJytS5__3l^Gu~Mp>z^iLo+D|%vC*u6wP4wB67BpWyBl`NUI(kOz z7bdMpQOs!P$R^OL6hTy?O=+e{h8?uu9uWQ!W~YPsdLykm7$S!7a|9oKt(#5UhEM}q zH6x`VPNAUP9&($vHyndg0pu@z8yD|(*L{rhcNksS%s%6(r=eDldItRb2+R8woNgl= z^%S&LPW=M)7sc;jjD+5%U)ZZyjaZ3&PuNM#t0c zXhYqiu|6inP+EsII(9LN0pX2~*SVcmO&&_d9%?wPck~%VYb?@Q%j;;FcV&4+u&sjy zw8`?hJ42|?6|{L_v*G8-R4(}ZweeGfO!hauYj~x_7Dcl~R&tJtV$U^-AsYJRTD3{Bo_kjx8 zW`M@eXLKutp8F4raq*kQF*G)y6MlZL;k|3!4vePf({3Zft|Ohn14mR($TJO~lYM+% zk2S&3&NU#9(8R0m2mHdNdd(uIH+ZX{%!ZSWdA)|zpThq~>fgox0rh&&<9U8a|L0^$ zrbp!d5|7Ke2*X`w_p^V~>*0oT?!lLaF4Ye;)W->dRDxc7YpMPTx1qN$8zP35v^7bq zO!FR5sE-RKK6`3vGDN&k@;P*bB_N(3ecdzmqp$s9P1_HPseBZ28s=$?*aH%K*Xbj4 z;Qd{b(mE7)oNo%kT#3FX@w21d>4iL?R5FG}^YndOPD^9x;Hzu^rDr!MCYf|%GQJIG zrlquGNLQobfN7w#^4Jrp@0%{kb#tbt5j*f^emXKt(DB&ZZlbW5Se&dwBRw_sx$%=G zKbJTq!#pRu6be-G3(dLEVKf^`oS>LtU>+S;mvutO7Z}Zp=FBaC<_kqN2hK^)&CNGM z*9Pu8lA|14jhvScw_)bU$ied6dw*0;EIDyJDi8Qh)Q zQm3*U6uz7#=F*~BWs9<*;AZYDIG(>?Zb^QgT9*-*Gcn1GYh}>#A{1oLmeh>Nnt=YL zxS;exM$TMk^phYMaW5+=DsY9|OkS#3OG(krw+2<6_ST6B=7~knLxD%zs`dQCeZa5_7h> z7;Qv8l4Su@E7U&mQS zJGtYjRUqlN%f@AETx@r0tD|P+Khq|tWfo_nK_R_&=2FtVhEo0ALc6QCYMFWY1=&lq zf+&wO)5mkIM6OZ|Wo-+1I+jp-i;CKvY$^7XY@CZnIW^NVn9`prVfKQ>a|`BXqac-K zm%iYvL6ROayVy~l%Ce(?8^}5#m0s;%_B#q*re|33AMyRJ{?zjS zz+X}mVdi=wP=^cWqCp;GPAD@M{2bEzRSlQy<|jEg!;9wrZ2 zRrPQ|L7vJ73bTbbPI7OosMiZK%j)oQ=j5a5T2izCYTmL-@@TL^2_ff$Ivpf+oYd%$ zRB8014G=}u=~f!2mE>pVQ43CEb14?)NYZ19b{|Sx=M|Tg4i4jk5=G%0T6{(Q8;wgO zUU&}eb-862$Ykd^UBdh9+&RFNA~Pst7+TCNgkF)kd1ijE-Fs3|8S(%sNYPc3E*R?V z7nGto!(f1XFDl6{nM(r$sLzzu-ys=F=Rhl-r0x#cASrq6q=Ql(`_W)a$`R+x!x{9* z#Ee89VT^_l!||Au(8vP~kh4-coe$$ls*#ji1Pz>|yKk7e2q|8&2&!N>AH|H~A;*+A zN^*X6p5LfTZg*p04AN*yBF$)=`PNy6c8-LWf(DF*`HNK3r3cTGx+zCxq!v;xMRKTU zV1a~|2X|qe3%R;Xb!J2kl1g4)HNwmj^Px&|2{dIwDQ;Qr9A2HBWsz4%Y1YRZIwb%P zD#p%sNU2Z7B}F-yHeg84huLb~thq~a3-Y_E37zuwelLbA%s5)6Hemqq2ceUhj0OSUHrW?{Jq#pna=g@K>1B)|NPl*nSKeT9$E${ zJ|E&Ieg5n)Eab)$``UWwvFq$UfG@TY&WL?;J@oRC`^yIW~Dg4g#i_y?WYT(ft_%RI}r-2{Wz+*J< z6B_tQ4g5U~JXQn8Yv2S8Y|+4pJif_Fa={mXA)(Mp^%>Z74Sl+bO73T9=rc6*nHu^`4gCii z`VTbpXEk&v=u&+K_MC?PoQg`}%hJ%ZH1uo@JzGQ1(a>`=^jr-+S3}R!(DO8OXTjlk z16ok^hnMEmARwa#MrW(~4CtTLp8@@^`ZI71CoW7^J*1?y$MqThuj?J{>k2Xzm zUG!ud7Ucq9Gq4y9JW>OX*1(Tx;5ZHZ zxCW-))b$xUX<;S*T`dQ38Te>Q%|F%!aC*E3PSC&>4V1aw!s4rfsCWYMlY2dyZ zI1(_4tmg6~*!MN`R1N$CnST@KAIKJH=r#?!Ugi(y{Ex8LWSCku6Al_5mEl1g9>RVt z!vi=xj9rmoPY#b@&7I&V_IJR_NAdMW6H4(R{fj2l*Zwk0eYOeOq8#+%m-Z#@!Jp&6K>J;1FU=$-eDSeq6U6WcE1d~CbS1jWq2BgQ`w6$ z?90<*65HR2Kb@TetbF9(Co(-Ab`#o%zsqnvhcj6KB1YjQJ)G3;qt*W~t2tHctXFGNa?W7t$Y)ii5~3MKI< zih{dw-Yto%tF-d%d&x-`uUL9fK>-g^DmH2I^SY`to{|Z5#Y%B-$W#S^u}$jKqz#WU%ZiG}Kai>fP_7@Y!XT%w zQj%r$xK8c2u0#eSHvJc%$acad&Y4?0-r5z1s$GirxH=QKmhs6;iq$ey`ffW5?xZCB z+ZBl;`Yr~giBNehrKf^G7OT~!PsAn}s`5O%mqYnzSx~yfvS1c!y^^|=+H#>#M8HE? z-L6nlG*4TSCGU96a78H*TMD!D(-+Ju&cG(UN>G=kl`5gq@;r@~(>R05L6_#Pn;5Gb zc2^jvZpckGd7=U(EXVL1&0{KzobNKmiepiE>Kx?ly-KVa=J#B^k2AIv{gwASj2$HN zXSid7PE3z`IyA%a{<*C88jGZ-2tFyT!RqsI##M&5kqP2anEFLn-zE56S-(5v(aM1L zFBpIKf!uRGET+;7i~Qs8L(l^*!?Y%<$Mg>x)3}`X$pjy*b?W;|`d3N*5R5;_%#h86 zvUx-{Kb6g%==X^)UN%c)vsyO)D4U+>uZhnhn}=kR)=>3f7#|~Cq41w(b`%~Y9i#NS zPbSi@57LeBwC1A6be7)Sq43i9g3>b?Cb=I({HJm6NtR=i-ESci{#TOuONPsWFd?7o zM~D1ra8L4WWFj4ZNhau|W1ak`wN-s2V_9-K(Y-42cMh3L7+Wa&PlFV4N8urNIb=dE zt-VrsjgUv~X2O38?`LE}?i>ma_)rvhIB&?NNp?SkOoV?8nUG&kCgeBEbgs7^>9U99 z4`ggFnUK%*)x$pvVDc|imaC*QFBd33&lCO((w9uces6Y?&}@HH8xeG8%wAQN=b`Aaa*7r>P7 z1XF$!{e2nc=>Qm)GPZ8XQ{6{+SbosShFMlJ~ zOPDk>_J%o2HcQAnjdDV!l1^~97qEV|tiK-e1`++Ya(XE8;Et#F2Z*0c=k*sbmk<8M zgooU9ByYZ~FCTQOA0&^bGfa1af$uHj`^ly^nefjHGZf)6l6gut8=#k8PAC5E0{r3c zF2Eds^dNsn%O<4{!P8_jPd1BXvqCnv$!4W&9+AycvPttul5;^eKa)+GKN9|yZ0b>u z2<|DH!Lm6>He+Nn8K!OwD8+P6nmtQTKy;l$tMTdqm+RF9 z&7jXk=x(O%qDo$M85p`4I?cXa=+5(S7oLyERMYGjy!nt{AO)Y(lo4d-8TUWK)MYTL z6|M4CMbgg4U0!uv6L8UZSi4zSaB;ZCx|&IHcPxA&+#NwZCGL*yoYC?z{ z(!{tMx@vl?rD`Y1T6*WHvX7YbJoN$&I<*Mr%VcA?wiCeNz73tE@Sad%9rX>xbu2e+Nhg`KV~ zWLf^=GUXu~RJJay)3-*b`!?KFzf|+(?gFfy_jY+(hu&%9Ethj;)%hMy=f`yrYE|>| zU?!#3KA3}=w+B;Qk@+?r{y3a-jpMO`~3SSuz7?$$!BGHmFq_O90*gfzgK8oi{ z_^JJ+`Rn_@DW4*5GZ1Qdb5!yOugH5FcC|d3r=3uQ$$SL9g`Zm9I>-wGj>1RJ?<(@@ zVOPsDB4fIf_aXSy^7cX=r60-bfuAC;33j!-UpzqG7vNLNI}dr3ZzK=Hc&EG$*wymN z;L#=El#l$S?;d_?d6!l4{6SN~7Xo2wdC6FxqBDQWr^uu8b!vHELEcs{QTPJzQ{=^K zax6nUF9^3Fh>4ICt|FMdjVl{btqb>d$=5|_?YDxV_nO^v+x$BoEV5r@XH<@@##iJ$tb5PYK_b8hMS7Hw_#l zk50cR;qygU!0OD8JS1*U=u` zc}hDp47`fGPc`zUK_0d5%BRSqx8l_D>LHK#DBnilr^usqFLk-fdVsuEjl9b$c~L5P z0cdB{^5#I^3E-4Z312VZ)ZuG{ysdCZ;fujf315^(o*9*b)?$=TkvB{uuNalc3?`B{ z5@_{`n-`=W*~Jxr><@REN@_zr9M_Jfb|SNRmak2HMrhC7;G z=cn+sX!zcLfIL4q(80CRPF{KdUz~>TI{4^Jit;J`j)X;>Z(EVhN;&-=n2C?l^J$H| zpa_Ykl{a6**Y5#*8#R392k;%#@C|qX-|sYhk>E>J0tXXh{{03Pb$s_CzDhkxl=%q0 zp^;YxdEJ$(ziarGfsfiN=MP80ZUeGhr+9k^+wSs~?9_=3Qv&bKqFbivTb8ILD5@^+vTqA|YmDe~ep z^6Zee7fh5c=-Qp~=-iV!eErY~tpi>86nRf;=%}dCqeq*~k4HzvnfdqK zZ+3p#k9+1O#Wxb_XouichR*MGlD;crXb<80cq7NGgSIb6uE#dKh_X%GnY9h?zJ=&X zGo(+VA?Do8K*NtMLDv6hV3EH9Z|K!NDF$8TJwwD*FTl|+L;u>#;;^uf@q#01H;sU{ zwSYtxz8d(|z)!9D7jGl7rZ9Az(gP6!MCakQeNibd-1 zz9-2gjaaf|?B0|A|9!G4?dhe=8PFh3y6cAd9~UFDFNp(mI{R21rJqkC3tI(RjBypH zH)?FnjinXM*A`W^G?u>Ea&6JO7F)C4-SP=(z|%E)3m(E^wh}syEjsaXxuO-&0!+7S0N4XueIU3}4>GyH}WIWa@GD0%5TC-=&9mm*j+Be=}H-PbC%&( zEY&RrCN`f5AlbW7uU|KtpnbR)vWvOw-Lh<> z<0oz;zpv37vI|ij=lMbVtC?B=U1C6@Pd&Y!hF2N*3ksoONun{t6{;%+U$`oPtNCzM z>U4FieO9j?a9QkhIjPm03%5_0!(6U+8{FY~0W|t8jLL+L*`D=A#Hb8zc%6?34;x(k zLh)P0k_+P~BXZ|aS?{`Mn1?eY^up6zouo3>12 zHnVNJLt>x-U)_4pTdrY}hJW5Jkt<5;b2s$?uUb~L4SzemiMJ>CXMe{dbR%A5nNTj3 z4mL1Ga6dqrKs{gn=xg^Whs6P0Ct43jA80kL#XAXoIDA^{1D((HI`;#VDiovBqS<^* zd_RhG$@eKo>k<9J$w?XZto7mYj9~K+)*4cdnj3vw91`}bCBD@cdI$%E z5vMNJ+gc5GRIjD?vHp8X>1Y2grT>kkv_CJU{l2x78bW(q3FGz2Ab2)PS^~p_B6}fk z;|$R9>BGygRC^Q{<-))9hPE{KefvqJZI=tR9eR-`L4T;PKO01PR*k~Sqc(9^G`;9T z8Y$0+=4?-{4VB*FG|E~gsU7qTr+1`vXs>obkL0Uv$wQVwG40>D9%n#5d52^przF@5~q-Z*s03 z;(@512#e0J3bs{vP1Y><%6#bvvC(l*<~t1PTUpT2ZSaNGT7nLOw;y6~D2r+)wLv8J zh|K>s+O?xm;r11JgZ*6TgVtcL7#ZeOKhQqbjq@CiC=xxdXnBhxNGyLu^VZwBt4__!jJoZXFKzUf44LLjqOYz zZ)bXcM>|9BXHtJC>ADrUrdg>3_>I%HB$01VaQ2H2*1wX+?=6{gNrOt5Hq5T0fe$wZ`*UN|=i67}`A-n7I;{4=R`n>pB67kSH zmvppr=J(Z+sVAuew#Xws)m z50}mrI%7^cS17-FI~Pm3>RciEc_+QA&K08Xr2z4-tIidor^{5@m2|RlTp0I1-YGxK z$w?Lib_vP7>x*z%VKboAb*8&Y5-}Q}D!(&*pvHaY(EQhRC}6=$_5JVDnJ`EdJ4)Y2 zSd=OWX-#dk27X!t7i-|98hEV+roDRQBl+hw@EKqc`z+bK4BQGVRg#T5=cfgn~GksYcMrfV%2ieMY{#)qcxG|r~GhdS0lk03gx1$ z^H|^mjybvx@@Z0t?!s|TzV!mB`hv0Z7@oQZK~P;Nl)I(?|eguZ|@uf=PA%0q1_MRy9Gv1S=due1Y zBz&3_Pdyg#Hvo2qJ014|Wp`y{0zM+cpUN=x=_I$0PNMVs4}g?5&V?Y&*~BQAhZ?wQ0YEhDB?4K{6S!7%;SxXISZL+VD^%UaPja!&Oc6<0Pg=B)hOok~x2&VBR@gJ4pU&`=h8NMOIo^rUA z{IJS!x*V@(Wq1vl$oFcPdUVOJ!lZnUm*v4LhF4zro_#3Fdz>tvmji_76&bFV`IU6{ zmgV#GhY(&4!2dkq0aIT_@mWVE(s_prEBSImhWUL=q%)0s=>8DD*N1fc*YCS@uGw-= z<2v8mxz+d%vZi5ukQ8Ur9i=*0-q|CmJNixm(cdaSP1Uq-T_U2P^Zv=@edk2t4JLXd z7rk@fo&BbZK_lLo0xB6A7H4nh67ua0`UCkif>mB>1a^)k_=>m!-OiDdQqFm_TISLC zp#=Ur*fg^K>&LYJe|1jV57klm&~)$*T^&OecAkf_!oC+jDtTz$obq0RT`iB=OY&3s6nUG0P|Mq+ zl1F&rqwpQaPc1JB0QFJIr^q`Bgj(Kll|1!1?F-;j%gcK}__>!o$|J67Ki~{4ImLb#O=F3&T%|FWsvkc{CoQuO4If!IG8#CqAOn zeRwtBeehv;;`|i8A8Yv1Q5mUCR6d387cw7}5&Cw6Z;@o>|H&`<{R(_2BE<^H)U$K_ zARhY21grB)2A>UOf#O1KE&1z*AEgQ8IluF$JXI=(iae^LYCa>zvLAQDm(6ErxtO74 z7tj4C&hx~?jf#!=C(rX#&*s5&p67|dN@|1HW;{##LpXO}KW16cug5+8EsuMK+a4`_#_8}nuIcXD zR=m%9)8FWScg?MzIToiGS{_P_t8;g-S%wx<;>fzuz5E7+=whw9;X!pXED_eUTmLB8 zZi%c55DYeYJ2%6k8w5W`{R;bo_IC{Pwu<)bt)e5L2xm~XTUH3hZ)X;h?@{ra`A5a~-`gtIeSTQ< zEI!oY;k)Nv`+eUQcisG?{dMz&aO)<^h1&CVkm$F;vRQODlil5KqlHX2yaq=2I|xi8#b7baBVJ;`tZFB#wbDQts;-TK3# zx8GsW*L+y)nH5;SviXaW%9bxmYTIpNSGL(krnT8(2VTiw1k*{II~Q-B{37vcuNjuz zwe@1#fW6pF)g91d_g{aIcJ*jKmQMqjx^vs{KQgC-&>o+R{o``{nsT(g_Q*C*Gt5T+H8? z#oNWL(K`)C5jJKYzWOL$I6hHRkr^@U)jFP;y1=11hi~tgvm2iws&Mmn2XE^jKfsf>QO=* z0e%E84zFyl${{$@&j9#-_?wb@4lR!fI2Xzso|YAE_Hy)OOxuX37XU2Nk=Jy#mfe!`)BV(0KdGa`xz% z)=yUz*;!(curVtu{t$6JrfYmiI4-0$r@9{l_c;A+$jz%~BhIwGR*uC|oXL9KvZro$ z$V1L|TaQtGKE`muXl41@<|dS@CcIgnmbI$c!#L?;fWU^t+iO=*e$TM@WKpf$>47}# zbq3hcDgB!;0I*wDDe=MZu`#`7HT;MV>COF!XSW*mlOH45&=Yle7UCbW>NqbAJ61h1 zVUK0?r-Sg{)|yo=#hpdG_q8ro@=NbnVbQn0rq|#4DkG8o;|7Iu2F2Qvk~KLVzQ*br z4ZoyS* z6D1$_hh8yubmX$HH&5yad&q|sJl58+G9^O4-NJ5uR8f&!krHHOktgt~s@{6IF3oOB z#4i`WGW;g9At&ne!qQ|v>xsGuyve#B&|*OQ>t>*PBxnJL3@w=k2}W7<+uQ03bb+KjtQ0 zgmmZh#B^?MPy8`AFDCwxn*o`D9-N*znVZ`)w{r7h<^^sB{qz9chys%FAy%m6u=tySz{>_|Ee3C%L>l+b`b!yYi)z(vW8;AyI5VS{o+4jZ%T-SwL*^o1>AE5P^g>;u{MSiFJ$oXHAA0Ny0 zx3^q>)8+d6W4ZqRQLev%a{Zkw*Wayj{r!_%fBW!s&&-^{&Fz^xxOp+NUe1r{{g^$q zTo*gTGu|Fo9xNEznRS3~Z>z4+7TeETZt;hWZ8IKT)qH)y8!gutywiTZ*DCaU9k{ayM@eE^|$Q5mafIDv}vNNmH)=x+SPpsFMiOFWjE5+#)Eq)e#OMu1LVn__Y7&on1j~Y3UTsc_yfQ1~6(x1VBl-Ss~m9V=Kz$Lkt&p!Y8EP9JP&0ScJ`wIH~?(OySX&ykMsUKBTy>QvX?b2&^`uK%! z(6=O*D%bAf@I!t%fZx+!TRRd_czU=OE={3>QukuH) ziFoB_uy38dC0=g|S|wV{yWHI_T?0Ol^Cp_7yL-T|-sXLC6IyeyG@&=;9pwUSGif=qBB*Clg)kIX4btXdf|j)y7`Kc?B1|HW&Vi~zTfdN>su^3 zeM`CiY)b{^1I$t_W|-MfkE^fU>17U^qe#A%ciBL0{or=0IiSsE@~m4WK5c%+81J}S zzC)bm_o|@>d=5?zUyn1*EO))WWwqYWVlz~WGtJfFvr!&{b*+MJhxm-20k(YDEKzKz z&ORHqL~fgF`}~JvK`XFT{&4INU2Bmo`-dAW39T`V0yP) zGBC#v*DA9x%)$I!b1)y8gZV3SFoL#oh~{7fUE~nW!3YWnQ|4gY9fxQR#@$IY%)z)j ziH13tk30u!z#J@njV;>C+gC8YY6{l3$H&kNnb}XT*^Cy5*`Hin%VU3G^%TVZ^EJAd zbrUi?YkBOyS#5BvNPLQLC4Nuvdm^yqWse!a za%tI}I(lf`MHC647fT~uHQd5g5(0Hk;eQ`updsXfb!PJrwXaIaa;>{-r z+Qy;fv4I@Az@d&<1BdXkIXG7&+BlTKp^n5X$(=;YjFa3+w9HUIXC2IP!Rs}|{)5$S z4x8TO9U!>978%UiPQjgqU zxTd<@(Yw0UqfDBCSGO-7P~BdYr_8{s+wTu}^GQB~$DF&mefh{h4$%x8l13UhL^JT} z_7zqehiC>4nl(#uC($OyN$w=tfuC#N^q5EM*oWAo@%C|R7UePX z&-2!Z6PXvCBz_U^%A2#7>|L@7bze7TmG}&EA2RmV{lkCx%4Gk_?XPaFZui`D?%wX# z+^8+JVNytSahJY^mnH8tHSG-PRkeDTJ8YO2uU&H{rAD)|;iUtn)5^wrR)cgtV_&}7 zM;PzwX<c2HT9}X_$}u1(E$3*l9K{LCS2LaQ~BLop_3ba}+r-E5q-erBWDGDh;H=WeczFp9ddzpxCpj%_=Chz(xR7WsC3 zL|YTBpD1G#Wqz!zr_lVF=FRW5B;>xAa#if7BR5ty%t?=h<-xS*jwLwJ1;peB`AnR z{#>d%%6irV>dsVE-c0^?-u#Ps6M&g8<;{QCkTbmYJa^+r#1AmPr8%rpPn3G|rwNx+ z&g=g9cI_io-u&+0dGk-?jk3z9naQRE+}&_9t=TYv=64}{E*orNk(l2l*4=vMh%R>O zjpCG@XtlOipnstnGkso4MbL`&*AHi6F8b=1^Q0NBPbkehRrA|3C(++oSW|v-1?8T3y-+*7jachn!JLQ2ELC(<6ag~fq5--~#=h%dz3OhcADLk>K+con-?;oso)Rfs6#*$VVfmS=Bj2&&K53-hYgBxbbXHV5~iQW}K6M`n1 zjDR4~q#r6^43Ax;Do1o_Rr~eWtS3x5U?4H%V3nh$bQ{CyJdwuqRgNe_M_ccM`j!m0 zhaj=^MnLd2MDHzN-qdODDnthie9b;U zyH~pz?O)M6s?rgq?`WHT0AmbYKlu9G$qT`@7?XE~B>+(DDQU#?jKvqwp_3sYQqQ_6 zinWgQn;GOG1Xz`IvDFsr$@mWbL`OS4G2_{0c{~;K?k|vMd^BY(!m0`Oo96Fp`C@*# zr7Uk{vxi|FtwO{M`Xru}%Pxkz%Z76Pao``t-l0Af%n$-I{6L3%Qho^p+;-yC_og&Hj`LlugMmBV8+wTwVf)6u-1Ld8o zwuX(g!au6x_Isy_QqnL#?YRBHiFp%XgMZeO&!O%39E`7w>O>~ga4i< zBk+IC2{y3f_Ks5tx5oPIYqyQ8Y`4XF2*G?;l~Uov3B)Xh=l@CUxfOpHytk#P_+qeE zQ!gREX_zpf$;<1BWSZR<+4B*`w@!L_#i`(`lcQD8o|CaE=;f2qDrn0|e_@0QUUPD& z3R-b;unJmya*zs|e{z5ddj4dX3d%kCunL+9Kl-WQ$tU}&pou4qDS=IVRK67LWbX4{ zvO1-HjqR&rwBJT^sx};s2*RNVMc>+1z*#sGaU4Gz@K2&h~nq)>ZPk^k-e}Fi#_cy-G0LA4oLU+q2c1HV($UDcIGbfS-Nf z=Q{Yg34T7ivG2-w>*b1t)}5ekx%SD%XIGNct=B>~?OaJxw_TgI>64Y@=ZMPei+ca$zQFp@6SM|GAk)M(9bC2mWa2ykdGzP7nZ+&LIHF zEuXB8x9(iM&^m2Ptyq0+*Ot&VB=x7)KHD;F4N2X9E$GEvYsk-o*PeOtGfq8p?e!Of z))Msy{5-0EW-a;oefU{xdVMYV8Q*vr^eU{W9TTm7wc^je?|e#jgr93d1h|rGhJ&kx#z_G>KJ#PGLC%C>@-sobxQ83~)~3obMw{ zzt&fQPI4zBROd}qpi{V}Hdcv~!Br(rMaZTjWFO`n6Q?0$A0TAE=OLR3Nz-}Arty$H zi;&gBhb;Ivllyl8P!71B<^Ej)l*g(-r;z;-&@6D~aL&ts=72Mgb6!EnK1Il8@sK@_ zkbQ=b&EX*{Xsi;S=OHUV$co|THPbP%5Fu;KIVP4gdP4Sfa2GQHp)bs$G`Rsv8Kis# znI&B2!p39bV)$OheQ$<;-{e$@3nBNIxD>Qy2Cm>K?`eS66+EA# z-_0g9>#z>>A7b7Ul;uyfnV^jh z*TeQK@NsaL6LwsjrSkzM3$!u7GEQ3x+GNm{bJ{Y{rh;bUwB?{p1FeG7Y@ki&A*%p>Ch)7Esbme(Y@Lo` zy9)GYIeiW2S)gy=?$!aH1DTt+%nhLBfwqOyHi0$^w5?&3N?SmiV>&KwL6|3~>HoUfxkf_nejd-j{m7?I2D;VVzxSr*`>D{uaM z8J$!()bjZV*e5wPCa=%!t5tj>W479z2Xgd3=(WG-19&kW_IK1ec1Cj(|W@; z!?HJ}ul^;R=((`w^$9;o@!`A1eFg6a>>7IuKOXOo6Fmlj4c=epE$jfqPxNqT8;AIb z9u8v-(SKj7$Nc@$8lsF_9Ja5$Dw}XP(IeyThwXE`sUsXt^vJm7(fb^M9ETG%SIdA*pAhy5$`Hr5ZAQ$p%ubVmRAKHEXBHpsz@b zW;e#z_W96yw8&Qhg%CYu-^FqbX$Dc?I(LWQ!2ESE@lbCi}Hhu~C$?JCkEk>{8x?RHC z6O!wm68g3n4ATG|o-nO$JX4^9fOIWS=@j^Yymv*|mvnTZVwZ40-oe@rT!RyLKcdR< zrh#z&{tDmn*eXXL$35l5bpU7JxR;!`6^T{YN8s|DxDMd5Bze#bC;7V~v&s=C$#dd5 zfD4u60auOiAyi>~+icGTQCOn;wy`3zJq4SM+g>8v3c@uIj@$eb`r2*1I3H~B-1ZV| zW^QYM&5zsspXzJ(l=xsXN_?>CB|g}65?{7IPOGujTPvju)MXVG)-pRwBGk9CK*I$b+U zFtqf=S!pj{)Q{*8qmT8JgM6D|iOs6HL3QSLO)TQKO;U^b{?|PiwV92%<~*7;=N;}( zEvZLr@}I=UD69604PT+l6y3?|{nYrnN_|{1)LB)xTovL17+{(Wu{cACRb=?Mjzh@|U=IG4W$= z24wolE%{UOZqUozq_bVRkI`r4+G6{AkNtA1_)_#$&oO*OXf{6y%*?bRvov3@w2Pm}q*6s^GJ9Mn8?#&GQN=a1U99I0Awyt|&*Rkjp1O?SNzJ}JpKG4!OXGyL zh7EAv)1m80V;|mT_bkB-`!{^`i`n~kD`x}3@XyJy$#V3N^PdgN6YsJ*rOc|!>kLat zYG3_6OZ?4mY{lcR>d;y{nsLUkFx4Neb7AVty79scM)m@3rxHjI+Gt^4yQA-cR*xmp zjwHo>KgTW}e4xE*t~8!-Vebz<;CRzZ8d12g%O5}B2;|tz02j6cSOdpyS7BF-Kj5%& z>_ru}1K2D{ZiGw1if0Zu;v~5$YzMHRl3b3hMtEs_@u88%4FR#fa;Z$^=Jwc4+`Jh3 zp)7&R|2AhBxRDup?40o`>QyYq~Vm#t{b_(?i z{FFd$-L0F4f@uU*`nT(gYv3kEYR5LBRH)mrjZ`Y47|sL6TUOT|xK1v-u4hc_xP9tW z#`u61ofmWbwH#Q!FO)o(tx_E7B53ev(8)6 zv)kG*Uf3n9PhvYF>%1L0Z`uz!QTGAXAv4ilzVq&1#6w3RDFJdHXAzL>ZP?ECWK#R< zElK@RxwO`v((L8^T=O$tG$Lh9_8U?%3!we(HF zdC;5wcQ-g5zPtX8p(Qxc(Bhxy)uKzF@eKBe#!Gud9c?3zOt5y`E_|2j5uKw~@4--g zqJCdF$9Mo|1f>4^x7fXY)7g8!(W&>Y;?V#6-dj1D?I*XBPs#1%%W^weFSnEaayvOi zZYQ_O?PP=8PAWGm(&cvYCApnc_B?;kFPhhvo(;itP9SNxZ-3;-w3E2)kcbms&@)?h zxWBOM%SlpQa_cXjp_R|^d~)h!@&sM@=S?W#7)>ov&gG^jrFipmxkKMJK!5ThO%~S~ zTg=)q{lOXAm8er^g8iL0g!mcTXsT1=Ek3^MYf++bs#u2-<(K4tcXcb3Dnm0klnd)ye-QgK; z>46&YmlHSeUwoJTH@usP^1b!O4Z;69+mU^P^g{piT{e7d+xu$ zcLVDxb{m~8&s~W-irRib*-uo)u(Y2Thdl}0$upAA|LCYcIT#u($;Kl4LO-=Mvm7jA z{H`iAA5PPSo&G8=9^7>Xd3EELEROEl;VYKq_LSX@X~25PNRl{xPKLL>{go1av$MlZ z@T`dxicd$&{u)Awr~g$7Lo|152<*aut=Yy}zDfZGRKVN8Jai?zil58^v+LQkQ$Gksqy&*kYGEg*$6-P>)8 zu(F>~BeFB8>o%52R+<*&gfjtf{aqJe#^c$GQQw#b-CHpJn|$Y)jAnem(e* zn#<1i*Z@A*mY+@C06y4kXE$sBA8ZvlqpUrUma8&`SyN%Iv4mSUz+89sqYb_rQ#Nkd zm~Z_E@P@M=ZOpU!A~iRi_1%_?lxht%A0)^{`b zV0-0k%4YDvwiCX61oKt+=8Kfv1K(0$zJ7M|<_|Y}Z<(|u-?|y_-m{yxHXD7V~KG^o3-TWf>U^{sB!xzDabUk#o24~*E3HuTFw;AS9 z`1c{qTKKmQ)MMgta9Pr8L~DGFD41)++=uCK@q0iy+=rQPRSzhS`|xbmG4TSR zS=?O~++6}Rhr7#xyFUVYp1aF~yUTzIxVu?!cZG*+4%~doL-suQKjR@Q0B>VXjaY~f z6(e*dXKKVUgm7773}R3S>NQXonnwUChNSB#uZwvIN+9h9d|ApvPzH%#S!%@PXDBBY z!d-KCAJ`Vd%{SqJuq_4u-z;UYEd#GToYH=|*%p1$V!-;-inb+3MAVt$<3{Cv`BT(d z?XmdRSGU`H<94}jKmfaE#0rD&RYSWi>zwyc>($z0xLY18^cQ*-2NoCOR_r-Y&UhzU z?C1xI-*MwD-fI?p{Z{dKwBcm&=^lbRZ}+8IIdsFT9?xFd3f=Zw#WKHyW_?(SSMAyq{r7GGea)H+4TRdDRjmls6mPXVR+; zqjZfupm{ry-=;~zs|{>$d;=SSHJnnc;S|%Yee{X(;dA=bGHX#w8n7SDXz{9BLnJlF?Pc$)NoqU88y@zkyU(ptGM4h!(&1nYq7AW zV~&co<|xr*-i`a@6zUn4%BH>u&2O66P`@jqFy1r>z73wjc4_oHf=18fwaoYDO(ite zj>ZFJxUbtsZHvOcZgS_+0gIR*RG>-9(bpZ-eZqle#V)4&p7#4RQ{4NS-(Iya9SOgTI1LMRnVHw0e{ z{$W#8e;OrhX@aJQR^NL85eBOi@_oo3^xviScXW20(Qwab+#&Xh@u|5a4vX0;{^F_r zHHceTrNyUVt5}b;dhez#I$aDi2UzRH`??^j{WSC|BpiiK@XBDEk7zadGy7GWbXTKS z`TEpK?9}*_uY-A}wXGaC(r>nvub$Xje+c-1@>MCTCWiA_TyJ5Zw1b0nN?Q(o3-Oyu zqsoE8veaSN!HE&ru>F9R0@_~({cs#w#37su(@XFh^4$g8*KXP7#7Q?82zSAWyFZ}X z@urb*CX?cJ`N(QVAjf4maUH-JIBuI0x58TOuyNc4C$0myEJw~STs2~ZP=$Yk6&8df=26TL5Sy4nZf=V?#LWva|G`a@Wdvpjpj!&Kxy|x6H!oQJ z#!XYkFw7J{&nV*Nwu~d(ypZt?H%&Pv)D_ERI>m-lcw=b<&SIboBo(bnW13y(i}7s& z-LTKY+HUVjxEpuRcw2w3=Pmb^hXh~Toa=peO&fOUv1;5097dtf-L<#*zRW1yK-?O% zJjCaUdP|QB9_?)0Sl=;RLkLdHS*iT6A!FOVIP&QYv>J~&npk+6UGMv=CiU5JWxTCk zqq#8gV(JB5pp^~ZUpGYPEtQibl#hI8IYEef3m>F}qnwO_-h=&s&I8(CHxH^G3HlX> z+*+o%NigoXa6J?I>)cvicH$&m6NLNNiMt3CC5xWGWg?edsPM8M^W`Z&<>tpzu5mLk zf0*w< zybyDPnSgpjt$E zK(z>^DCP}052zNQv{kRBG5Cwl=EP6zp3@Q zm<-Q84ZU$bh;Gk^TW8;xFp=7_Gp(y^Xuqhxo0^PXj18;1mH2jyFiaTRlr-@moiXZNaoTd#lFlk_~KB7QM%HU-jJ4E6XqOS)N$QOc~cCPQm6!@m|2 zYs7vy#eBTf)2{QtNfAR2VY}Eb+#T&dvwvngCJwN?Bc6{)5Tx-{5cYw4*Lj8Q3mXbO zJ8tb~mghE}zu6Njq?coi_q^_SwcC<$KOC*y<(PBgli}NOqRHRVr{NcOriWb=lee*! zv!;_Rde72J4|bIdS2JZ-GvR8o)79*6aWxsP=E$y|g{v&5s}bMgDl0rHwlvAt;pv-P zzg@J#{Tm4DG`N4#>3++{l`UTSvp0Ks8t-|E7koTbam=u6Z2C6pN8d_a_pQ_&O)ulz zT3<_01LY6$X4c188T#3s;uzL)&MzR?|I)deJ@u5@SAF#DHj9yu=P$(m3a8{S(#8HV zvznW1!DnQybDg+mSelzKTGdIib-I_tetOTE8ZlB|Jgz(ZkZ$mi;2)%?n+~3J%v?Nf zt9UtjC`Pwy{liY`te(q?#{~(+&#)g$0dx;?yhsk4f)Gq)51Kho%~{~wIqsk zW+`u%WEYGvPo9%+o?TRsmtSZup1U}|z`S_)+`_#4#pcL?d4tV!OU*?K%FIQx%q7`{ zv-3xID{q-Vm3KF8m;7sP3ybgqN_K%H+dL~fx2&kdoSR)(SX5@t$u~#AyDt89c9T=I zh+a@B&MwKGM=z>qU3YaqHFwVJ;>d!z^X8TfcE*t&S}A}wy!;X7@nv{&rIh<$IxoAR z05Z$wWEYx8kEGCam8XP9k~=SZ@qa?9Ha|2n=N5jeOgXi9_~pQA0`qO@NV;(&3yKzL zyeL}aN{VhgC_|~C*K6i2m?u3}V~&i88W}gZ`!qtvE)VPAVIe5K@BrKYPW+dC3TU0n} z?(78;@=-Ozf?_Cb&ZnF&Et^|VU@p!t%%chnUr^z*i;D~9=I5E`7NXAOoAae77fA5D zqPztK`C+Q|K<&jCv;4pTDsd^&VA0$X2qo@uiHT#(k>e&!F;6ZkDkwG2LIaafGS8ee zFDE}Q4_>906y+lNMJ1)?xZyET(T@+GA3IzMvh%ZZ=0aHstZt7b4WG!|dw5Lr z(tEp`jJs_m+a{gA&Is4LM$`y(eb>mH>suu^TR7dF>lAfK z^x*Fo6MZNC(K|++<^Lb<-UKkJBHJ6U+e^~vB+yxa5Fm6Gpdk=C2}=YBQw>&BRbrnqMk#u=7cT#$q3a0eO!sTS}N9BL1f+_u+3ig+GS366DZ|5@% z(Uj~e-KByl{Vv=yD>*5Br3$8W3tbY+os^!Sf+^ii7sGNVrJKC`g>m^h=<;M$dVvb2 z{2_FyFLzRY1!63M=b!D3z%4!AFa>(?^v&Kl*m?4XDWJ9E6s1Xz*1-KWa96ub{`6yc z`7W=%#`65zR4~==oL7IObVGo`q?CW7SAV2*C6&P(v44398hDTf9;|_fXkeQLR^Mt& zcl!?G_3@QI%ojvRkF6V2AD>I~1rgHYl1%^f;hJ>++Wd68QC`7ux_XQ^YIkG1QMjO4 z<@D%ZYNyA>KDE=Mzo?xa`lxn#=%3o@`K{i*7(E|7)i5qhRyjSl6yuA~^INjj@IW7c zke>g4m0i^c`|K zwQM--(tRMq>oI1AV?6(x40m8m2}gUK2dp?MPf)28!GVA&Y95|&v?tj-Q5>ZY(7=N= zFx>%9QFe?;;b?!djiNZp{|gQLf(HIr10UDGWJ5-AM2~FRD30KffC=SxJmJtcx{F+K zlwP2LS8CwLHSi07iQcn#!lAEkY0^K}z^7z^&KN9b+Kb0Xr_kSWEzpV^?IDrigGH@$QAW zpXwvQgIKBzTY36mHd%(LCxqjO_d*$#>SJT8WtgU@a0dN`-LVWTzsg|Wq+(fl242nP z<#Hw>ySz!j&wS`N{`LZ+#uZI;$W0+$1BRw}$&cI-w%x7jq zMP7-8J*!!T6*Bg%WRI$B+`t_R6;ck3%)$lv%NF@@>O-5u z7vu_G3l{P^sn^zBxF+i`Wq5hGy&`|neYY0UJNxYXd5hHcz91I`E0?}0%%pDzISUqm zRYiO-SVl$hWY{oW0;6(VY?+b;u?&fIKj}~wiCxssD|BD;ft*O60cIC3Rx^Y4Q{m5A zwPe0h6zEOPFPBJ)i}Dw$;;Cheic7OI70E#0x0MxF6wWSztW}6oZWUFMy?>D^Y$QnP ztIFW3mZ~h2uTo}BLJ1^)J-NzFKbvrVxP^S?kd)5g(+b|H#4^c5>?#!}>Tac~7l~_u zB0pk5u@I+{yniJoEh?FpuT~6IGS7V7oVi7K1(TFXl?NQC5@r@I$X)55IxAxqy;=K5 zaw$^(T3jmfuTTETT!7suHz&^%C0iCT3uIea3mJpy24*gn>@}Dd;IWeBg{&v}a zMfP{g{_C>8U-sXY{W{q{B>Nx9{wK0uFZ+jOKa%QS3R7R?FNN(d8JFA7-ksLSb`;^8R=WeNBtiSg1?fnZ^;MFMegN_)R*WRMrc@24b^giKQKLZgQ1!vyDLI9U3QnuC&}&-&Ny zkJL9tW8JW>8aGu<;U^wGPIn#4yDN{Tgl|B-LclO{!{2P%P8=8rr9%)M=BGa4EcOna+pP zRqL*gD=Dux9n;rhtn*k>`pjFB@+&IJis!-pJI1CT>&S)_h8eG2rcs)V7{0iR#b3h1 zQlC359;K0^w^bV7ZiGi;v9{b0#3>HL9(Tj>7sEYsa0s;XCHf-)6AnF8-V{7)dJe!F z@hDEAhhg3iuO2Xc7gd~s_W&Z)A66U>Zdq`-Z4a|@zw*6-p3WE;B{*7EIk;b_jJW6c$X2O=FbZLJcuJX(-#

^sbD@ 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 K4DFjAq4$6LAd(6I literal 0 HcmV?d00001 diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/Lib/libafbrs50_m4_fpu_os.a b/src/drivers/distance_sensor/broadcom/afbrs50/Lib/libafbrs50_m4_fpu_os.a new file mode 100644 index 0000000000000000000000000000000000000000..ae1c4f1d890b810159f029b8958240f14eb15517 GIT binary patch literal 186050 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 KV;(d)bpHaF?IqFx literal 0 HcmV?d00001 diff --git a/src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.c b/src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.c new file mode 100644 index 0000000000..520a438bf3 --- /dev/null +++ b/src/drivers/distance_sensor/broadcom/afbrs50/argus_hal_test.c @@ -0,0 +1,1174 @@ +/*************************************************************************//** + * @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. + *****************************************************************************/ + + +/******************************************************************************* + * Include Files + ******************************************************************************/ +#include "argus_hal_test.h" + +#include "platform/argus_print.h" +#include "platform/argus_s2pi.h" +#include "platform/argus_timer.h" +#include "platform/argus_nvm.h" +#include "platform/argus_irq.h" + +/******************************************************************************* + * Definitions + ******************************************************************************/ + +/*! An error log message via print(); */ +#define error_log(fmt, ...) print("ERROR: " fmt "\n", ##__VA_ARGS__) + +/******************************************************************************* + * Prototypes + ******************************************************************************/ +static status_t TimerPlausibilityTest(void); +static status_t TimerWraparoundTest(void); +static status_t SpiConnectionTest(s2pi_slave_t slave); +static status_t SpiInterruptTest(s2pi_slave_t slave); +static status_t GpioModeTest(s2pi_slave_t slave); +static status_t TimerTest(s2pi_slave_t slave); + +static status_t CheckTimerCounterValues(uint32_t hct, uint32_t lct); +static status_t SPITransferSync(s2pi_slave_t slave, uint8_t *data, uint8_t size); +static status_t ConfigureDevice(s2pi_slave_t slave, int8_t rcoTrim); +static status_t TriggerMeasurement(s2pi_slave_t slave, uint16_t samples); +static status_t AwaitDataReady(s2pi_slave_t slave, uint32_t timeout_ms); +static status_t ReadEEPROM(s2pi_slave_t slave, uint8_t *eeprom); +static status_t ReadRcoTrim(s2pi_slave_t slave, int8_t *RcoTrim); +static status_t RunMeasurement(s2pi_slave_t slave, uint16_t samples); + +static void DataReadyCallback(void *param); + +extern uint32_t EEPROM_ReadChipId(uint8_t const *eeprom); +extern argus_module_version_t EEPROM_ReadModule(uint8_t const *eeprom); +extern status_t EEPROM_Read(s2pi_slave_t slave, uint8_t address, uint8_t *data); +extern uint8_t hamming_decode(uint8_t const *code, uint8_t *data); + +/****************************************************************************** + * Variables + ******************************************************************************/ + +/******************************************************************************* + * Code + ******************************************************************************/ + +status_t Argus_VerifyHALImplementation(s2pi_slave_t spi_slave) +{ + status_t status = STATUS_OK; + + print("########################################################\n"); + print("# Running HAL Verification Test - " HAL_TEST_VERSION "\n"); + print("########################################################\n\n"); + + print("1 > 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); }