diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index b70bbfb2ab41..0d3cec2a13d6 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -71,6 +71,7 @@ add_subdirectory_ifdef(CONFIG_VEAA_X_3 veaa_x_3) add_subdirectory_ifdef(CONFIG_VOLTAGE_DIVIDER voltage_divider) add_subdirectory_ifdef(CONFIG_XBR818 xbr818) add_subdirectory_ifdef(CONFIG_TACH_ENE_KB1200 ene_tach_kb1200) +add_subdirectory_ifdef(CONFIG_MB7040 mb7040) zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/sensor.h) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index cd1a6a47728e..338329cc91d6 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -143,6 +143,7 @@ source "drivers/sensor/ist8310/Kconfig" source "drivers/sensor/lm35/Kconfig" source "drivers/sensor/lm75/Kconfig" source "drivers/sensor/lm77/Kconfig" +source "drivers/sensor/mb7040/Kconfig" source "drivers/sensor/mhz19b/Kconfig" source "drivers/sensor/nct75/Kconfig" source "drivers/sensor/ntc_thermistor/Kconfig" diff --git a/drivers/sensor/mb7040/CMakeLists.txt b/drivers/sensor/mb7040/CMakeLists.txt new file mode 100644 index 000000000000..d363b8f5881e --- /dev/null +++ b/drivers/sensor/mb7040/CMakeLists.txt @@ -0,0 +1,5 @@ +#SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(mb7040.c) diff --git a/drivers/sensor/mb7040/Kconfig b/drivers/sensor/mb7040/Kconfig new file mode 100644 index 000000000000..65df5ee092b4 --- /dev/null +++ b/drivers/sensor/mb7040/Kconfig @@ -0,0 +1,19 @@ +#MB7040 ultrasonic sensor config + +#Copyright 2025 Sabrina Simkhovich +#SPDX-License-Identifier: Apache-2.0 + +config MB7040 + bool "MB7040 ultrasonic distance sensor" + default y + depends on DT_HAS_MAXBOTIX_MB7040_ENABLED + select I2C + help + Enable driver for MB7040 distance sensor. + +config MB7040_DELAY_MS + int "Delay in milliseconds to wait for distance measurements" + default 100 + depends on MB7040 + help + Timeout for sample fetch. diff --git a/drivers/sensor/mb7040/mb7040.c b/drivers/sensor/mb7040/mb7040.c new file mode 100644 index 000000000000..d96f3442c45d --- /dev/null +++ b/drivers/sensor/mb7040/mb7040.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2025 Sabrina Simkhovich + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT maxbotix_mb7040 + +#include +#include +#include +#include +#include +#include + +#define RANGE_CMD 0x51 + +#define MB7040_HAS_STATUS_GPIO DT_ANY_INST_HAS_PROP_STATUS_OKAY(status_gpios) + +LOG_MODULE_REGISTER(mb7040, CONFIG_SENSOR_LOG_LEVEL); + +struct mb7040_data { + uint16_t distance_cm; + struct k_sem read_sem; +#if MB7040_HAS_STATUS_GPIO + struct gpio_callback gpio_cb; +#endif +}; + +struct mb7040_config { + struct i2c_dt_spec i2c; + uint8_t i2c_addr; +#if MB7040_HAS_STATUS_GPIO + struct gpio_dt_spec status_gpio; +#endif +}; + +#if MB7040_HAS_STATUS_GPIO +static void status_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + struct mb7040_data *data = CONTAINER_OF(cb, struct mb7040_data, gpio_cb); + + k_sem_give(&data->read_sem); +} +#endif + +static int mb7040_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + const struct mb7040_config *cfg = (struct mb7040_config *)dev->config; + struct mb7040_data *data = (struct mb7040_data *)dev->data; + uint8_t cmd = RANGE_CMD; + int ret; + uint8_t read_data[2]; + + if (chan != SENSOR_CHAN_DISTANCE && chan != SENSOR_CHAN_ALL) { + LOG_ERR("Sensor only supports distance"); + return -EINVAL; + } + + k_sem_reset(&data->read_sem); + +#if MB7040_HAS_STATUS_GPIO + /* Check if status_gpio port is present */ + if (cfg->status_gpio.port != NULL) { + /* Enable interrupt before writing */ + ret = gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_EDGE_FALLING); + if (ret != 0) { + LOG_ERR("Failed to configure interrupt: %d", ret); + return ret; + } + } +#endif + + /* Write range command to sensor */ + ret = i2c_reg_write_byte_dt(&cfg->i2c, cfg->i2c_addr, cmd); + + if (ret != 0) { + LOG_ERR("I2C write failed with error %d", ret); +#if MB7040_HAS_STATUS_GPIO + /* Disable interrupt before returning error*/ + if (cfg->status_gpio.port != NULL) { + gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_DISABLE); + } +#endif + return ret; + } + + ret = k_sem_take(&data->read_sem, K_MSEC(CONFIG_MB7040_DELAY_MS)); + +#if MB7040_HAS_STATUS_GPIO + /* Check if status_gpio port is present */ + if (cfg->status_gpio.port != NULL) { + gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_DISABLE); + if (ret != 0) { + /* Success: interrupt fired and semaphore taken */ + return ret; + } + } else { + if (ret != -EAGAIN) { + /* Should not happen: semaphore only given by GPIO interrupt */ + return ret; + } + } +#else + if (ret != -EAGAIN) { + /* Should not happen: semaphore only given by GPIO interrupt */ + return ret; + } +#endif + /* + * Small wait due to device specific internal i2c timings. 10ms is a common + * wait to time to ensure ultrasonic sensors achieve stability and accuracy. + */ + k_msleep(10); + + ret = i2c_read_dt(&cfg->i2c, read_data, 2); + + if (ret != 0) { + LOG_ERR("I2C read failed with error %d", ret); +#if MB7040_HAS_STATUS_GPIO + if (cfg->status_gpio.port != NULL) { + gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_DISABLE); + } +#endif + return ret; + } + + /* Convert MSB/LSB to distance in cm */ + data->distance_cm = (read_data[0] << 8) | read_data[1]; + return 0; +} + +static int mb7040_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct mb7040_data *data = (struct mb7040_data *)dev->data; + + if (chan != SENSOR_CHAN_DISTANCE) { + LOG_ERR("Sensor only supports distance"); + return -ENOTSUP; + } + + /* Meters */ + val->val1 = data->distance_cm / 100; + /* Micrometers */ + val->val2 = (data->distance_cm % 100) * 10000; + + return 0; +} + +static DEVICE_API(sensor, mb7040_api) = { + .sample_fetch = mb7040_sample_fetch, + .channel_get = mb7040_channel_get, +}; + +static int mb7040_init(const struct device *dev) +{ + const struct mb7040_config *cfg = (struct mb7040_config *)dev->config; + struct mb7040_data *data = (struct mb7040_data *)dev->data; + + k_sem_init(&data->read_sem, 0, 1); + + if (!i2c_is_ready_dt(&cfg->i2c)) { + LOG_ERR("I2C not ready!"); + return -ENODEV; + } + /* Initialize status GPIO if present */ +#if MB7040_HAS_STATUS_GPIO + if (cfg->status_gpio.port != NULL) { + if (!gpio_is_ready_dt(&cfg->status_gpio)) { + LOG_ERR("Status GPIO not ready"); + return -ENODEV; + } + + int ret = gpio_pin_configure_dt(&cfg->status_gpio, GPIO_INPUT); + + if (ret < 0) { + LOG_ERR("Failed to configure status GPIO: %d", ret); + return ret; + } + + gpio_init_callback(&data->gpio_cb, status_gpio_callback, BIT(cfg->status_gpio.pin)); + ret = gpio_add_callback(cfg->status_gpio.port, &data->gpio_cb); + if (ret < 0) { + LOG_ERR("Failed to add GPIO callback: %d", ret); + return ret; + } + LOG_INF("MB7040 initialized with status GPIO"); + } +#else + LOG_INF("MB7040 initialized"); +#endif + return 0; +} + +#define MB7040_DEFINE(inst) \ + static struct mb7040_data mb7040_data_##inst = { \ + .distance_cm = 0, \ + }; \ + static const struct mb7040_config mb7040_config_##inst = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .i2c_addr = DT_INST_REG_ADDR(inst), \ + IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, status_gpios), \ + (.status_gpio = GPIO_DT_SPEC_INST_GET(inst, status_gpios),)) }; \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, mb7040_init, NULL, &mb7040_data_##inst, \ + &mb7040_config_##inst, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, &mb7040_api); + +DT_INST_FOREACH_STATUS_OKAY(MB7040_DEFINE) diff --git a/dts/bindings/sensor/maxbotix,mb7040.yaml b/dts/bindings/sensor/maxbotix,mb7040.yaml new file mode 100644 index 000000000000..253a0b6d7f01 --- /dev/null +++ b/dts/bindings/sensor/maxbotix,mb7040.yaml @@ -0,0 +1,12 @@ +description: MB7040 ultrasonic distance sensor + +compatible: "maxbotix,mb7040" + +include: [sensor-device.yaml, i2c-device.yaml] + +properties: + status-gpios: + type: phandle-array + description: | + GPIO pin connected to Pin 2 (Address Announce/Status) to monitor sensor + state. Pin is high during range reading and low when ready for I2C communication. diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi index 220e0e9969c4..43c1125095b6 100644 --- a/tests/drivers/build_all/sensor/i2c.dtsi +++ b/tests/drivers/build_all/sensor/i2c.dtsi @@ -1382,3 +1382,9 @@ test_i2c_lsm9ds1_mag: lsm9ds1_mag@b9 { compatible = "st,lsm9ds1_mag"; reg = <0xb9>; }; + +test_i2c_mb7040: mb7040@ba { + compatible = "maxbotix,mb7040"; + reg = <0xba>; + status-gpios = <&test_gpio 0 0>; +};