/** * * @license MIT License * * Copyright (c) 2024 lewis he * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * @file SensorBHI260AP.cpp * @author Lewis He (lewishe@outlook.com) * @date 2024-05-27 * */ #include "SensorBHI260AP.hpp" #include "bosch/BoschParseStatic.hpp" #define BHY2_RLST_CHECK(ret, str, val) \ do \ { \ if (ret) \ { \ log_e(str); \ return val; \ } \ } while (0) static constexpr uint16_t max_process_buffer_size = 512; SensorBHI260AP::SensorBHI260AP(): comm(nullptr), hal(nullptr), staticComm(nullptr), _rst(-1), _error_code(0), _processBuffer(nullptr), _processBufferSize(max_process_buffer_size), _firmware_stream(nullptr), _firmware_size(0), _write_flash(false), _boot_from_flash(false), _force_update(false), _max_rw_length(-1), _accuracy(0), _debug(false), _process_callback(nullptr), _process_callback_user_data(nullptr), _event_callback(nullptr), _debug_callback(nullptr) { } SensorBHI260AP::~SensorBHI260AP() { if (_processBuffer) { free(_processBuffer); } _processBuffer = NULL; } void SensorBHI260AP::setPins(int rst) { _rst = rst; } #if defined(ARDUINO) bool SensorBHI260AP::begin(TwoWire &wire, uint8_t addr, int sda, int scl) { if (!beginCommonStatic(comm, staticComm, hal, wire, addr, sda, scl)) { return false; } return initImpl(BHY2_I2C_INTERFACE); } bool SensorBHI260AP::begin(SPIClass &spi, uint8_t csPin, int mosi, int miso, int sck) { if (!beginCommonStatic(comm, staticComm, hal, spi, csPin, mosi, miso, sck)) { return false; } return initImpl(BHY2_SPI_INTERFACE); } #elif defined(ESP_PLATFORM) #if defined(USEING_I2C_LEGACY) bool SensorBHI260AP::begin(i2c_port_t port_num, uint8_t addr, int sda, int scl) { if (!beginCommonStatic(comm, staticComm, hal, port_num, addr, sda, scl)) { return false; } return initImpl(BHY2_I2C_INTERFACE); } #else //USEING_I2C_LEGACY bool SensorBHI260AP::begin(i2c_master_bus_handle_t handle, uint8_t addr) { if (!beginCommonStatic(comm, staticComm, hal, handle, addr)) { return false; } return initImpl(BHY2_I2C_INTERFACE); } #endif //USEING_I2C_LEGACY bool SensorBHI260AP::begin(spi_host_device_t host, spi_device_handle_t handle, uint8_t csPin, int mosi, int miso, int sck) { if (!beginCommonStatic(comm, staticComm, hal, host, handle, csPin, mosi, miso, sck)) { return false; } return initImpl(BHY2_SPI_INTERFACE); } #endif //ARDUINO bool SensorBHI260AP::begin(CommInterface interface, SensorCommCustom::CustomCallback callback, SensorCommCustomHal::CustomHalCallback hal_callback, uint8_t addr) { if (!beginCommCustomCallback(interface, callback, hal_callback, addr, comm, hal)) { return false; } return initImpl(static_cast(interface)); } /** * @brief reset * @note Reset sensor * @retval None */ void SensorBHI260AP::reset() { if (_rst != -1) { hal->digitalWrite(_rst, HIGH); hal->delay(5); hal->digitalWrite(_rst, LOW); hal->delay(10); hal->digitalWrite(_rst, HIGH); hal->delay(5); } } /** * @brief update * @note Update sensor fifo data * @retval None */ void SensorBHI260AP::update() { if (!_processBuffer) { log_e("Process buffer is empty."); return; } bhy2_get_and_process_fifo(_processBuffer, _processBufferSize, _bhy2.get()); } /** * @brief setBootFromFlash * @note Set whether to start from external flash * @param boot_from_flash: true boot form flash or boot form ram * @retval None */ void SensorBHI260AP::setBootFromFlash(bool boot_from_flash) { _boot_from_flash = boot_from_flash; } /** * @brief getHandler * @note Get the native BHI API handle * @retval handle */ bhy2_dev *SensorBHI260AP::getHandler() { return _bhy2.get(); } /** * @brief setInterruptCtrl * @note Set the interrupt control mask * @param data: * BHY2_ICTL_DISABLE_FIFO_W * BHY2_ICTL_DISABLE_FIFO_NW * BHY2_ICTL_DISABLE_STATUS_FIFO * BHY2_ICTL_DISABLE_DEBUG * BHY2_ICTL_DISABLE_FAULT * BHY2_ICTL_ACTIVE_LOW * BHY2_ICTL_EDGE * BHY2_ICTL_OPEN_DRAIN * * @retval true is success , false is failed */ bool SensorBHI260AP::setInterruptCtrl(uint8_t data) { _error_code = bhy2_set_host_interrupt_ctrl(data, _bhy2.get()); if (_error_code != BHY2_OK) { return false; } return true; } /** * @brief getInterruptCtrl * @note Get interrupt control info * @retval SensorBHI260APCtrl class */ SensorBHI260APControl SensorBHI260AP::getInterruptCtrl() { uint8_t data; _error_code = bhy2_get_host_interrupt_ctrl(&data, _bhy2.get()); SensorBHI260APControl result; if (_error_code == BHY2_OK) { result.wakeUpFIFOEnabled = !(data & BHY2_ICTL_DISABLE_FIFO_W); result.nonWakeUpFIFOEnabled = !(data & BHY2_ICTL_DISABLE_FIFO_NW); result.statusFIFOEnabled = !(data & BHY2_ICTL_DISABLE_STATUS_FIFO); result.debuggingEnabled = !(data & BHY2_ICTL_DISABLE_DEBUG); result.faultEnabled = !(data & BHY2_ICTL_DISABLE_FAULT); result.interruptIsActiveLow = (data & BHY2_ICTL_ACTIVE_LOW); result.interruptIsPulseTriggered = (data & BHY2_ICTL_EDGE); result.interruptPinDriveIsOpenDrain = (data & BHY2_ICTL_OPEN_DRAIN); } return result; } /** * @brief isReady * @note Query whether the sensor is ready * @retval 1 OK , 0 Not ready */ bool SensorBHI260AP::isReady() { uint8_t boot_status = 0; _error_code = bhy2_get_boot_status(&boot_status, _bhy2.get()); log_d("boot_status:0x%x", boot_status); if (_error_code != BHY2_OK) { return false; } return (boot_status & BHY2_BST_HOST_INTERFACE_READY); } /** * @brief getKernelVersion * @note Get the sensor firmware kernel version * @retval 2 bytes */ uint16_t SensorBHI260AP::getKernelVersion() { uint16_t version = 0; _error_code = bhy2_get_kernel_version(&version, _bhy2.get()); if ((_error_code != BHY2_OK) && (version == 0)) { return 0; } log_d("Boot successful. Kernel version %u.", version); return version; } /** * @brief onEvent * @note Registered sensor event callback function * @param callback: Callback Function * @retval None */ void SensorBHI260AP::onEvent(SensorEventCallback callback) { _event_callback = callback; } /** * @brief removeEvent * @note Remove sensor event callback function * @retval None */ void SensorBHI260AP::removeEvent() { _event_callback = NULL; } /** * @brief onResultEvent * @note Registered sensor result callback function , The same sensor ID can register multiple event callbacks. * Please note that you should not register the same event callback repeatedly. * @param sensor_id: Sensor ID , see enum BoschSensorID * @param callback: Callback Function * @param *user_data: user data,can be null * @retval bool true-> Success false-> failure */ bool SensorBHI260AP::onResultEvent(BoschSensorID sensor_id, SensorDataParseCallback callback, void *user_data) { if (!bhy2_is_sensor_available(sensor_id, _bhy2.get())) { log_e("%s not present", getSensorName(sensor_id)); return false; } return _callback_manager.add(sensor_id, callback, user_data); } /** * @brief removeResultEvent * @note Remove the registered result callback function * @param sensor_id: Sensor ID , see enum BoschSensorID * @param callback: Callback Function * @retval bool true-> Success false-> failure */ bool SensorBHI260AP::removeResultEvent(BoschSensorID sensor_id, SensorDataParseCallback callback) { if (!bhy2_is_sensor_available(sensor_id, _bhy2.get())) { log_e("%s not present", getSensorName(sensor_id)); return false; } return _callback_manager.remove(sensor_id, callback); } /** * @brief setProcessBufferSize * @note The default value is 512Bytes , Must be called before initialization * @param size: The set buffer size is requested by malloc, and if PSRAM is enabled, it is requested from PSRAM * @retval None */ void SensorBHI260AP::setProcessBufferSize(uint32_t size) { if (_processBuffer) { log_e("The buffer size must be set before begin"); return; } _processBufferSize = size; } /** * @brief uploadFirmware * @note Update BHI sensor firmware * @param *firmware: Firmware data address * @param length: Firmware data length * @param write2Flash: 1 is written to external flash, 0 is written to RAM * @retval true success or failed */ bool SensorBHI260AP::uploadFirmware(const uint8_t *firmware, uint32_t length, bool write2Flash) { uint8_t sensor_error; uint8_t boot_status; log_d("Upload Firmware ..."); _error_code = bhy2_get_boot_status(&boot_status, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_get_boot_status failed!", false); if (write2Flash) { if (boot_status & BHY2_BST_FLASH_DETECTED) { uint32_t start_addr = BHY2_FLASH_SECTOR_START_ADDR; uint32_t end_addr = start_addr + length; log_d("Flash detected. Erasing flash to upload firmware"); _error_code = bhy2_erase_flash(start_addr, end_addr, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_erase_flash failed!", false); } else { log_e("Flash not detected"); return false; } log_d("Loading firmware into FLASH."); _error_code = bhy2_upload_firmware_to_flash(firmware, length, _bhy2.get(), _process_callback, _process_callback_user_data); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_upload_firmware_to_flash failed!", false); log_d("Loading firmware into FLASH Done"); } else { log_d("Loading firmware into RAM."); log_d("upload size = %lu", length); _error_code = bhy2_upload_firmware_to_ram(firmware, length, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_upload_firmware_to_ram failed!", false); log_d("Loading firmware into RAM Done"); } _error_code = bhy2_get_error_value(&sensor_error, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_get_error_value failed!", false); if (sensor_error != BHY2_OK) { _error_code = bhy2_get_error_value(&sensor_error, _bhy2.get()); log_e("%s", get_sensor_error_text(sensor_error)); return false; } if (write2Flash) { log_d("Booting from FLASH."); _error_code = bhy2_boot_from_flash(_bhy2.get()); } else { log_d("Booting from RAM."); _error_code = bhy2_boot_from_ram(_bhy2.get()); } BHY2_RLST_CHECK(_error_code != BHY2_OK, "_bhy2 boot failed!", false); _error_code = bhy2_get_error_value(&sensor_error, _bhy2.get()); if (sensor_error) { log_e("%s", get_sensor_error_text(sensor_error)); return false; } return sensor_error == BHY2_OK; } /** * @brief getError * @note Get the error status string * @retval string */ const char *SensorBHI260AP::getError() { snprintf(_err_buffer, 128, "API:%s\nSensor:%s\n", get_api_error(_error_code), get_sensor_error_text(_error_code)); return static_cast(_err_buffer); } /** * @brief configure * @note Sensor Configuration * @param sensor_id: Sensor ID , see enum BoschSensorID * @param sample_rate: Data output rate, unit: HZ * @param report_latency_ms: Report interval in milliseconds * @return bool true-> Success false-> failure */ bool SensorBHI260AP::configure(uint8_t sensor_id, float sample_rate, uint32_t report_latency_ms) { if (!bhy2_is_sensor_available(sensor_id, _bhy2.get())) { log_e("%s not present", getSensorName(sensor_id)); return false; } _error_code = bhy2_set_virt_sensor_cfg(sensor_id, sample_rate, report_latency_ms, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_set_virt_sensor_cfg failed!", false); log_d("Enable %s at %.2fHz.", get_sensor_name(sensor_id), sample_rate); return true; } /** * @brief configureRange * @note Set range of the sensor * @param sensor_id: Sensor ID , see enum BoschSensorID * @param range: Range for selected SensorID. See Table 79 in BHY260 datasheet 109 page * @retval bool true-> Success false-> failure */ bool SensorBHI260AP::configureRange(uint8_t sensor_id, uint16_t range) { _error_code = bhy2_set_virt_sensor_range(sensor_id, range, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_set_virt_sensor_range failed!", false); return true; } /** * @brief getConfigure * @note Get sensor configuration * @param sensor_id: Sensor ID , see enum BoschSensorID * @retval SensorConfig */ SensorConfig SensorBHI260AP::getConfigure(uint8_t sensor_id) { bhy2_virt_sensor_conf conf; bhy2_get_virt_sensor_cfg(sensor_id, &conf, _bhy2.get()); log_d("range:%u sample_rate:%f latency:%lu sensitivity:%u\n", conf.range, conf.sample_rate, conf.latency, conf.sensitivity); return conf; } /** * @brief getScaling * @note Get sensor scale factor * @param sensor_id: Sensor ID , see enum BoschSensorID * @retval scale factor */ float SensorBHI260AP::getScaling(uint8_t sensor_id) { return get_sensor_default_scaling(sensor_id); } /** * @brief setFirmware * @note Set the firmware * @param *image: firmware data address * @param image_len: firmware length * @param write_flash: true : write to flash otherwise ram * @param force_update: true, rewrite to flash or ram regardless of whether there is firmware, false, do not write if firmware is detected * @retval None */ void SensorBHI260AP::setFirmware(const uint8_t *image, size_t image_len, bool write_flash, bool force_update) { _firmware_stream = image; _firmware_size = image_len; _write_flash = write_flash; _force_update = force_update; } /** * @brief getSensorName * @note Get sensor name * @param sensor_id: Sensor ID , see enum BoschSensorID * @retval sensor name */ const char *SensorBHI260AP::getSensorName(uint8_t sensor_id) { return get_sensor_name(sensor_id); } // Get an accuracy report uint8_t SensorBHI260AP::getAccuracy() { return _accuracy; } /** * @brief digitalRead * @note Read GPIO level, only for custom firmware * @param pin: see BHI260AP_aux_BMM150_BME280_Expand_GPIO example * @param pullup: true is set pullup or input mode * @retval 1 is high ,0 is low */ uint8_t SensorBHI260AP::digitalRead(uint8_t pin, bool pullup) { if (pin > JTAG_DIO)return 0; uint32_t pin_mask = pin | BHY2_GPIO_SET; if (pullup) { pin_mask |= (BHY2_INPUT_PULLUP << 8); } else { pin_mask |= (BHY2_INPUT << 8); } bhy2_set_virt_sensor_cfg(BoschSensorID::GPIO_EXP, (float)pin_mask, 0, _bhy2.get()); pin_mask = pin /*GetCmd*/; bhy2_set_virt_sensor_cfg(BoschSensorID::GPIO_EXP, (float)pin_mask, 0, _bhy2.get()); bhy2_virt_sensor_conf conf; bhy2_get_virt_sensor_cfg(BoschSensorID::GPIO_EXP, &conf, _bhy2.get()); uint8_t level = conf.sample_rate; return level; } /** * @brief hal->digitalWrite * @note Write GPIO level, only for custom firmware * @param pin: see BHI260AP_aux_BMM150_BME280_Expand_GPIO example * @param level: 1 is high ,0 is low * @retval None */ void SensorBHI260AP::digitalWrite(uint8_t pin, uint8_t level) { if (pin > JTAG_DIO)return; uint32_t pin_mask = pin | (BHY2_OUTPUT << 8) | (level << 6) | BHY2_GPIO_SET ; bhy2_set_virt_sensor_cfg(BoschSensorID::GPIO_EXP, (float)pin_mask, 0, _bhy2.get()); } /** * @brief disableGpio * @note Disable GPIO function * @param pin: see BHI260AP_aux_BMM150_BME280_Expand_GPIO example * @retval None */ void SensorBHI260AP::disableGpio(uint8_t pin) { if (pin > JTAG_DIO)return; uint32_t pin_mask = pin | (BHY2_OPEN_DRAIN << 8) | BHY2_GPIO_SET; bhy2_set_virt_sensor_cfg(BoschSensorID::GPIO_EXP, (float)pin_mask, 0, _bhy2.get()); } /** * @brief setDebugKernel * @note Whether to enable chip debug output , Must be called before begin, otherwise it will be invalid * @param enable: true Enable message debug , false disable debug , Requires firmware support, the default firmware will not output any messages * @retval None */ void SensorBHI260AP::setDebugKernel(bool enable) { _debug = enable; } /** * @brief setDebugCallback * @param cb: Sensor debug output callback function , Requires firmware support, the default firmware will not output any messages * @retval None */ void SensorBHI260AP::setDebugCallback(SensorDebugMessageCallback cb) { _debug_callback = cb; } /** * @brief getPhySensorInfo * @note Get all information about physical sensors * @param sens_id: ID of the physical sensor * @retval BoschPhySensorInfo Class */ #define INT4_TO_INT8(INT4) ((int8_t)(((INT4) > 1) ? -1 : (INT4))) BoschPhySensorInfo SensorBHI260AP::getPhySensorInfo(uint8_t sens_id) { BoschPhySensorInfo result; struct bhy2_phys_sensor_info psi; memset(&psi, 0, sizeof(psi)); if (!_bhy2.get()) return result; uint16_t param_id = (uint16_t)(0x0120 | sens_id); if (param_id >= 0x0121 && param_id <= 0x0160) { int8_t assert_rslt = (bhy2_get_phys_sensor_info(sens_id, &psi, _bhy2.get())); if (assert_rslt == BHY2_OK) { result.sensor_type = psi.sensor_type; result.driver_id = psi.driver_id; result.driver_version = psi.driver_version; result.power_current = psi.power_current / 10.f; result.curr_range = psi.curr_range.u16_val; result.irq_status = psi.flags & 0x01; result.master_intf = (psi.flags >> 1) & 0x0F; result.power_mode = (psi.flags >> 5) & 0x07; result.slave_address = psi.slave_address; result.gpio_assignment = psi.gpio_assignment; result.curr_rate = psi.curr_rate.f_val; result.num_axis = psi.num_axis; struct bhy2_orient_matrix ort_mtx = { 0 }; ort_mtx.c[0] = INT4_TO_INT8(psi.orientation_matrix[0] & 0x0F); ort_mtx.c[1] = INT4_TO_INT8(psi.orientation_matrix[0] >> 8); ort_mtx.c[2] = INT4_TO_INT8(psi.orientation_matrix[1] & 0x0F); ort_mtx.c[3] = INT4_TO_INT8(psi.orientation_matrix[1] >> 8); ort_mtx.c[4] = INT4_TO_INT8(psi.orientation_matrix[2] & 0x0F); ort_mtx.c[5] = INT4_TO_INT8(psi.orientation_matrix[2] >> 8); ort_mtx.c[6] = INT4_TO_INT8(psi.orientation_matrix[3] & 0x0F); ort_mtx.c[7] = INT4_TO_INT8(psi.orientation_matrix[3] >> 8); ort_mtx.c[8] = INT4_TO_INT8(psi.orientation_matrix[4] & 0x0F); for (int i = 0; i < 9; ++i) { result.orientation_matrix[i] = ort_mtx.c[i]; } result.reserved = psi.reserved; } } return result; } /** * @brief getSensorInfo * @note Get all information about sensors * @retval BoschSensorInfo Class */ BoschSensorInfo SensorBHI260AP::getSensorInfo() { BoschSensorInfo sensorInfo; if (bhy2_get_product_id(&sensorInfo.product_id, _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_product_id failed!"); } if (bhy2_get_kernel_version(&sensorInfo.kernel_version, _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_kernel_version failed!"); } if (bhy2_get_user_version(&sensorInfo.user_version, _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_user_version failed!"); } if (bhy2_get_rom_version(&sensorInfo.rom_version, _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_rom_version failed!"); } if (bhy2_get_host_status(&sensorInfo.host_status, _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_host_status failed!"); } if (bhy2_get_feature_status(&sensorInfo.feat_status, _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_feature_status failed!"); } if (bhy2_get_boot_status(&sensorInfo.boot_status, _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_boot_status failed!"); } if (bhy2_get_error_value(&sensorInfo.sensor_error, _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_error_value failed!"); } if (sensorInfo.info) { for (uint8_t i = 0; i < BHY2_SENSOR_ID_MAX; i++) { if (bhy2_is_sensor_available(i, _bhy2.get())) { if (bhy2_get_sensor_info(i, &sensorInfo.info[i], _bhy2.get()) != BHY2_OK) { log_e("bhy2_get_sensor_info [%u] failed!", i); } } } } sensorInfo.dev = _bhy2.get(); return sensorInfo; } /** * @brief setMaxiTransferSize * @note Set the maximum number of bytes transmitted by the interface , Called before begin * @param size_of_bytes: The maximum transmission bytes of different platforms are different. * Set it according to the platform. If not set, the default is I2C 32 bytes, SPI 256 bytes. * @retval None */ void SensorBHI260AP::setMaxiTransferSize(uint16_t size_of_bytes) { if (_processBuffer) { log_e("Must be called before begin"); return; } _max_rw_length = size_of_bytes; } /** * @brief setUpdateProcessCallback * @note Set the callback function of the firmware update process to obtain the update progress * @param callback: callback function * @param *user_data: user data, can be nullptr * @retval None */ void SensorBHI260AP::setUpdateProcessCallback(ProcessCallback callback, void *user_data) { _process_callback = callback; } /** * @brief availableSensorNums * @note Get the number of available sensors * @retval available sensors */ uint8_t SensorBHI260AP::availableSensorNums() { return _sensor_available_nums; } /** * @brief Set the axis remapping for the sensor based on the specified orientation. * * This function allows you to configure the sensor's axis remapping according to a specific * physical orientation of the chip. By passing one of the values from the SensorRemap enum, * you can ensure that the sensor data is correctly interpreted based on how the chip is placed. * * @param remap An enumeration value from SensorRemap that specifies the desired axis remapping. * @return Returns true if the axis remapping is successfully set; false otherwise. */ bool SensorBHI260AP::setRemapAxes(SensorRemap remap) { if (remap > BOTTOM_LAYER_BOTTOM_LEFT_CORNER) { log_e("Invalid SensorRemap value passed to setRemapAxes!"); return false; } // Acceleration - related orientation matrices for different mounting directions struct bhy2_orient_matrix acc_matrices[] = { // P0 mounting direction, default direction, axis direction is consistent with the default coordinate system {1, 0, 0, 0, 1, 0, 0, 0, 1}, // P1 mounting direction, P0 is rotated 90° clockwise around the Z - axis {0, -1, 0, 1, 0, 0, 0, 0, 1}, // P2 mounting direction, P0 is rotated 180° clockwise around the Z - axis {-1, 0, 0, 0, -1, 0, 0, 0, 1}, // P3 mounting direction, P0 is rotated 270° clockwise around the Z - axis {0, 1, 0, -1, 0, 0, 0, 0, 1}, // P4 mounting direction, P0 is flipped vertically (rotated 180° around the X - axis) {1, 0, 0, 0, -1, 0, 0, 0, -1}, // P5 mounting direction, P4 is rotated 90° clockwise around the Z - axis {0, 1, 0, 1, 0, 0, 0, 0, -1}, // P6 mounting direction, P4 is rotated 180° clockwise around the Z - axis {-1, 0, 0, 0, 1, 0, 0, 0, -1}, // P7 mounting direction, P4 is rotated 270° clockwise around the Z - axis {0, -1, 0, -1, 0, 0, 0, 0, -1} }; // Gyroscope - related orientation matrices for different mounting directions struct bhy2_orient_matrix gyro_matrices[] = { // P0 mounting direction, default direction, axis direction is consistent with the default coordinate system {1, 0, 0, 0, 1, 0, 0, 0, 1}, // P1 mounting direction, P0 is rotated 90° clockwise around the Z - axis {0, -1, 0, 1, 0, 0, 0, 0, 1}, // P2 mounting direction, P0 is rotated 180° clockwise around the Z - axis {-1, 0, 0, 0, -1, 0, 0, 0, 1}, // P3 mounting direction, P0 is rotated 270° clockwise around the Z - axis {0, 1, 0, -1, 0, 0, 0, 0, 1}, // P4 mounting direction, P0 is flipped vertically (rotated 180° around the X - axis) {1, 0, 0, 0, -1, 0, 0, 0, -1}, // P5 mounting direction, P4 is rotated 90° clockwise around the Z - axis {0, 1, 0, 1, 0, 0, 0, 0, -1}, // P6 mounting direction, P4 is rotated 180° clockwise around the Z - axis {-1, 0, 0, 0, 1, 0, 0, 0, -1}, // P7 mounting direction, P4 is rotated 270° clockwise around the Z - axis {0, -1, 0, -1, 0, 0, 0, 0, -1} }; // Set the orientation matrix for the accelerometer _error_code = bhy2_set_orientation_matrix(BHY2_PHYS_SENSOR_ID_ACCELEROMETER, acc_matrices[remap], _bhy2.get()); if (_error_code != BHY2_OK) { log_e("Set acceleration orientation matrix failed!"); return false; } // Set the orientation matrix for the gyroscope _error_code = bhy2_set_orientation_matrix(BHY2_PHYS_SENSOR_ID_GYROSCOPE, gyro_matrices[remap], _bhy2.get()); if (_error_code != BHY2_OK) { log_e("Set gyroscope orientation matrix failed!"); return false; } return true; } /** * @brief bootFromFlash * @note Boot from external flash * @retval bool true-> Success false-> failure */ bool SensorBHI260AP::bootFromFlash() { int8_t rslt; uint8_t boot_status, feat_status; uint8_t error_val = 0; uint16_t tries = 300; /* Wait for up to little over 3s */ log_d("Waiting for firmware verification to complete"); do { _error_code = bhy2_get_boot_status(&boot_status, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_get_boot_status failed!", false); if (boot_status & BHY2_BST_FLASH_VERIFY_DONE) { break; } hal->delay(10); } while (tries--); _error_code = bhy2_get_boot_status(&boot_status, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_get_boot_status failed!", false); print_boot_status(boot_status); if (boot_status & BHY2_BST_HOST_INTERFACE_READY) { if (boot_status & BHY2_BST_FLASH_DETECTED) { /* If no firmware is running, boot from Flash */ log_d("Booting from flash"); rslt = bhy2_boot_from_flash(_bhy2.get()); if (rslt != BHY2_OK) { log_e("%s. Booting from flash failed.\r\n", get_api_error(rslt)); _error_code = bhy2_get_regs(BHY2_REG_ERROR_VALUE, &error_val, 1, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_get_regs failed!", false); if (error_val) { log_e("%s\r\n", get_sensor_error_text(error_val)); } return false; } _error_code = bhy2_get_boot_status(&boot_status, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_get_boot_status failed!", false); print_boot_status(boot_status); if (!(boot_status & BHY2_BST_HOST_INTERFACE_READY)) { /* hub is not ready, need reset hub */ log_d("Host interface is not ready, triggering a reset"); _error_code = bhy2_soft_reset(_bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_soft_reset failed!", false); } _error_code = (bhy2_get_feature_status(&feat_status, _bhy2.get())); BHY2_RLST_CHECK(_error_code != BHY2_OK, "Reading Feature status failed, booting from flash failed!", false); } else { log_e("Can't detect external flash"); return false; } } else { log_e("Host interface is not ready"); return false; } log_d("Booting from flash successful"); return true; } void SensorBHI260AP::print_boot_status(uint8_t boot_status) { log_d("Boot Status : 0x%02x: ", boot_status); if (boot_status & BHY2_BST_FLASH_DETECTED) { log_d("Flash detected. "); } if (boot_status & BHY2_BST_FLASH_VERIFY_DONE) { log_d("Flash verify done. "); } if (boot_status & BHY2_BST_FLASH_VERIFY_ERROR) { log_d("Flash verification failed. "); } if (boot_status & BHY2_BST_NO_FLASH) { log_d("No flash installed. "); } if (boot_status & BHY2_BST_HOST_INTERFACE_READY) { log_d("Host interface ready. "); } if (boot_status & BHY2_BST_HOST_FW_VERIFY_DONE) { log_d("Firmware verification done. "); } if (boot_status & BHY2_BST_HOST_FW_VERIFY_ERROR) { log_d("Firmware verification error. "); } if (boot_status & BHY2_BST_HOST_FW_IDLE) { log_d("Firmware halted. "); } } void SensorBHI260AP::parseData(const struct bhy2_fifo_parse_data_info *fifo, void *user_data) { if (user_data != this) { return; } #ifdef BHI260AP_PARSE_DATA_DUMP log_i("ID:[%d]:%s: DATA LEN:%u", fifo->sensor_id, get_sensor_name(fifo->sensor_id), fifo->data_size); SensorLibDumpBuffer(fifo->data_ptr, fifo->data_size); #endif _callback_manager.call(fifo->sensor_id, fifo->data_ptr, fifo->data_size, fifo->time_stamp); } void SensorBHI260AP::parseMetaEvent(const struct bhy2_fifo_parse_data_info *callback_info, void *user_data) { if (user_data != this) { return; } uint8_t meta_event_type = callback_info->data_ptr[0]; uint8_t byte1 = callback_info->data_ptr[1]; uint8_t byte2 = callback_info->data_ptr[2]; const char *event_text; // Remove warning UNUSED(byte1); UNUSED(byte2); UNUSED(event_text); if (callback_info->sensor_id == BHY2_SYS_ID_META_EVENT) { event_text = "[META EVENT]"; } else if (callback_info->sensor_id == BHY2_SYS_ID_META_EVENT_WU) { event_text = "[META EVENT WAKE UP]"; } else { return; } switch (meta_event_type) { case BHY2_META_EVENT_FLUSH_COMPLETE: log_i("%s Flush complete for sensor id %u", event_text, byte1); break; case BHY2_META_EVENT_SAMPLE_RATE_CHANGED: log_i("%s Sample rate changed for sensor id %u", event_text, byte1); break; case BHY2_META_EVENT_POWER_MODE_CHANGED: log_i("%s Power mode changed for sensor id %u", event_text, byte1); break; case BHY2_META_EVENT_ALGORITHM_EVENTS: log_i("%s Algorithm event", event_text); break; case BHY2_META_EVENT_SENSOR_STATUS: log_i("%s Accuracy for sensor id %u changed to %u", event_text, byte1, byte2); _accuracy = byte2; break; case BHY2_META_EVENT_BSX_DO_STEPS_MAIN: log_i("%s BSX event (do steps main)", event_text); break; case BHY2_META_EVENT_BSX_DO_STEPS_CALIB: log_i("%s BSX event (do steps calib)", event_text); break; case BHY2_META_EVENT_BSX_GET_OUTPUT_SIGNAL: log_i("%s BSX event (get output signal)", event_text); break; case BHY2_META_EVENT_SENSOR_ERROR: log_i("%s Sensor id %u reported error 0x%02X", event_text, byte1, byte2); break; case BHY2_META_EVENT_FIFO_OVERFLOW: log_i("%s FIFO overflow", event_text); break; case BHY2_META_EVENT_DYNAMIC_RANGE_CHANGED: log_i("%s Dynamic range changed for sensor id %u", event_text, byte1); break; case BHY2_META_EVENT_FIFO_WATERMARK: log_i("%s FIFO watermark reached", event_text); break; case BHY2_META_EVENT_INITIALIZED: log_i("%s Firmware initialized. Firmware version %u", event_text, ((uint16_t)byte2 << 8) | byte1); break; case BHY2_META_TRANSFER_CAUSE: log_i("%s Transfer cause for sensor id %u", event_text, byte1); break; case BHY2_META_EVENT_SENSOR_FRAMEWORK: log_i("%s Sensor framework event for sensor id %u", event_text, byte1); break; case BHY2_META_EVENT_RESET: log_i("%s Reset event", event_text); break; case BHY2_META_EVENT_SPACER: return; default: log_i("%s Unknown meta event with id: %u", event_text, meta_event_type); break; } if (_event_callback) { _event_callback(meta_event_type, byte1, byte2); } } void SensorBHI260AP::parseDebugMessage(const struct bhy2_fifo_parse_data_info *callback_info, void *user_data) { if (user_data != this) { return; } uint8_t msg_length = 0; uint8_t debug_msg[17] = { 0 }; /* Max payload size is 16 bytes, adds a trailing zero if the payload is full */ if (!callback_info) { log_i("Null reference"); return; } msg_length = callback_info->data_ptr[0]; memcpy(debug_msg, &callback_info->data_ptr[1], msg_length); debug_msg[msg_length] = '\0'; /* Terminate the string */ log_d("[DEBUG MSG]: %s", debug_msg); if (_debug_callback) { _debug_callback((const char *)debug_msg); } } bool SensorBHI260AP::initImpl(bhy2_intf interface) { uint8_t product_id = 0; if (_rst != -1) { hal->pinMode(_rst, OUTPUT); } reset(); _bhy2 = std::make_unique(); BHY2_RLST_CHECK(!_bhy2, " Device handler malloc failed!", false); if (_max_rw_length == -1) { #if defined(ARDUINO_ARCH_ESP32) switch (interface) { case BHY2_I2C_INTERFACE: // esp32s3 test I2C maximum read and write is 64 bytes _max_rw_length = 64; break; case BHY2_SPI_INTERFACE: // esp32s3 test SPI maximum read and write is 256 bytes _max_rw_length = 256; break; default: return false; } #else // Other platforms,I2C 32 Bytes , SPI 256 Bytes _max_rw_length = interface == BHY2_I2C_INTERFACE ? 32 : 256; #endif } _error_code = bhy2_init(interface, SensorCommStatic::sensor_static_read_data, SensorCommStatic::sensor_static_write_data, SensorCommStatic::sensor_static_delay_us, _max_rw_length, staticComm.get(), _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_init failed!", false); _error_code = bhy2_soft_reset(_bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "reset _bhy2 failed!", false); _error_code = bhy2_get_product_id(&product_id, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_get_product_id failed!", false); /* Check for a valid product ID */ if (product_id != BHY2_PRODUCT_ID) { log_e("Product ID read 0x%02X. Expected 0x%02X", product_id, BHY2_PRODUCT_ID); return false; } else { log_i("BHI260/BHA260 found. Product ID read 0x%02X", product_id); } // Set default interrupt configure uint8_t data = 0, data_exp; bhy2_get_host_interrupt_ctrl(&data, _bhy2.get()); data &= ~BHY2_ICTL_DISABLE_STATUS_FIFO; /* Enable status interrupts */ if (_debug) { data &= ~BHY2_ICTL_DISABLE_DEBUG; /* Enable debug interrupts */ } else { data |= BHY2_ICTL_DISABLE_DEBUG; /* Disable debug interrupts */ } data &= ~BHY2_ICTL_EDGE; /* Level */ data &= ~BHY2_ICTL_ACTIVE_LOW; /* Active high */ data &= ~BHY2_ICTL_OPEN_DRAIN; /* Push-pull */ data_exp = data; bhy2_set_host_interrupt_ctrl(data, _bhy2.get()); bhy2_get_host_interrupt_ctrl(&data, _bhy2.get()); if (data != data_exp) { log_d("Expected Host Interrupt Control (0x07) to have value 0x%x but instead read 0x%x\r\n", data_exp, data); } /* Config status channel */ bhy2_set_host_intf_ctrl(BHY2_HIF_CTRL_ASYNC_STATUS_CHANNEL, _bhy2.get()); bhy2_get_host_intf_ctrl(&data, _bhy2.get()); if (!(data & BHY2_HIF_CTRL_ASYNC_STATUS_CHANNEL)) { log_d("Expected Host Interface Control (0x06) to have bit 0x%x to be set\r\n", BHY2_HIF_CTRL_ASYNC_STATUS_CHANNEL); } if (_boot_from_flash) { if (!bootFromFlash()) { return false; } if (_force_update) { if ((_firmware_stream == NULL) || (_firmware_size == 0)) { log_e("No valid firmware is set. Please use the \"setFirmware\" method to set the valid firmware."); return false; } _error_code = bhy2_soft_reset(_bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "reset _bhy2 failed!", false); log_i("Force update firmware."); if (!uploadFirmware(_firmware_stream, _firmware_size, _write_flash)) { log_e("uploadFirmware failed!"); return false; } } } else { if ((_firmware_stream == NULL) || (_firmware_size == 0)) { log_e("No valid firmware is set. Please use the \"setFirmware\" method to set the valid firmware."); return false; } // ** Upload firmware to RAM **// if (!uploadFirmware(_firmware_stream, _firmware_size, false)) { log_e("uploadFirmware failed!"); return false; } } uint16_t version = getKernelVersion(); BHY2_RLST_CHECK(!version, "getKernelVersion failed!", false); log_i("Boot successful. Kernel version %u.", version); _error_code = bhy2_register_fifo_parse_callback(BHY2_SYS_ID_META_EVENT, BoschParseStatic::parseMetaEvent, this, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_register_fifo_parse_callback failed!", false); _error_code = bhy2_register_fifo_parse_callback(BHY2_SYS_ID_META_EVENT_WU, BoschParseStatic::parseMetaEvent, this, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_register_fifo_parse_callback failed!", false); if (_debug) { _error_code = bhy2_register_fifo_parse_callback(BHY2_SYS_ID_DEBUG_MSG, BoschParseStatic::parseDebugMessage, this, _bhy2.get()); BHY2_RLST_CHECK(_error_code != BHY2_OK, "bhy2_register_fifo_parse_callback parseDebugMessage failed!", false); } //Set process buffer #if defined(ARDUINO_ARCH_ESP32) if (psramFound()) { _processBuffer = (uint8_t *)ps_malloc(_processBufferSize); // In older versions of esp-core, even if psramFound returns true, it may not initialize psram correctly. // This situation is common when OPI type SPI-RAM is selected as QSPI, or QSPI is selected as OPI. if (!_processBuffer) { log_e("malloc psram buffer failed,try to alloc sram buffer!"); _processBuffer = (uint8_t *)malloc(_processBufferSize); } } else { _processBuffer = (uint8_t *)malloc(_processBufferSize); } #else _processBuffer = (uint8_t *)malloc(_processBufferSize); #endif BHY2_RLST_CHECK(!_processBuffer, "process buffer malloc failed!", false); _error_code = bhy2_get_and_process_fifo(_processBuffer, _processBufferSize, _bhy2.get()); if (_error_code != BHY2_OK) { log_e("bhy2_get_and_process_fifo failed"); return false; } /* Update the callback table to enable parsing of sensor hintr_ctrl */ bhy2_update_virtual_sensor_list(_bhy2.get()); /* Get present virtual sensor */ bhy2_get_virt_sensor_list(_bhy2.get()); // Only register valid sensor IDs for (uint8_t i = 0; i < BHY2_SENSOR_ID_MAX; i++) { if (bhy2_is_sensor_available(i, _bhy2.get())) { _sensor_available_nums++; bhy2_register_fifo_parse_callback(i, BoschParseStatic::parseData, this, _bhy2.get()); } } return _error_code == BHY2_OK; }