Skip to content

Commit b95aa48

Browse files
committed
drivers: sensor: mb7040: add support for MB7040 ultrasonic sensor
This commit adds a new driver for the MaxBotix MB7040 ultrasonic rangefinder. The driver uses I2C communication to read range data from the sensor and exposes it via the Zephyr sensor API. Tested on an esp32-s3 board using I2C bus. Verified readings at multiple distances to confirm accuracy. Signed-off-by: Sabrina Simkhovich <sabrinasimkhovich@gmail.com>
1 parent cabf2c4 commit b95aa48

File tree

7 files changed

+253
-0
lines changed

7 files changed

+253
-0
lines changed

drivers/sensor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ add_subdirectory_ifdef(CONFIG_VEAA_X_3 veaa_x_3)
7171
add_subdirectory_ifdef(CONFIG_VOLTAGE_DIVIDER voltage_divider)
7272
add_subdirectory_ifdef(CONFIG_XBR818 xbr818)
7373
add_subdirectory_ifdef(CONFIG_TACH_ENE_KB1200 ene_tach_kb1200)
74+
add_subdirectory_ifdef(CONFIG_MB7040 mb7040)
7475

7576
zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/sensor.h)
7677

drivers/sensor/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ source "drivers/sensor/ist8310/Kconfig"
143143
source "drivers/sensor/lm35/Kconfig"
144144
source "drivers/sensor/lm75/Kconfig"
145145
source "drivers/sensor/lm77/Kconfig"
146+
source "drivers/sensor/mb7040/Kconfig"
146147
source "drivers/sensor/mhz19b/Kconfig"
147148
source "drivers/sensor/nct75/Kconfig"
148149
source "drivers/sensor/ntc_thermistor/Kconfig"

drivers/sensor/mb7040/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#SPDX-License-Identifier: Apache-2.0
2+
3+
zephyr_library()
4+
5+
zephyr_library_sources(mb7040.c)

drivers/sensor/mb7040/Kconfig

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#MB7040 ultrasonic sensor config
2+
3+
#Copyright 2025 Sabrina Simkhovich <sabrinasimkhovich @gmail.com>
4+
#SPDX-License-Identifier: Apache-2.0
5+
6+
config MB7040
7+
bool "MB7040 ultrasonic distance sensor"
8+
default y
9+
depends on DT_HAS_MAXBOTIX_MB7040_ENABLED
10+
select I2C
11+
help
12+
Enable driver for MB7040 distance sensor.
13+
14+
config MB7040_DELAY_MS
15+
int "Delay in milliseconds to wait for distance measurements"
16+
default 100
17+
depends on MB7040
18+
help
19+
Timeout for sample fetch.

drivers/sensor/mb7040/mb7040.c

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright (c) 2025 Sabrina Simkhovich <sabrinasimkhovich@gmail.com>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT maxbotix_mb7040
8+
9+
#include <zephyr/kernel.h>
10+
#include <zephyr/drivers/sensor.h>
11+
#include <zephyr/drivers/i2c.h>
12+
#include <zephyr/device.h>
13+
#include <zephyr/logging/log.h>
14+
#include <zephyr/drivers/gpio.h>
15+
16+
#define RANGE_CMD 0x51
17+
18+
#define MB7040_HAS_STATUS_GPIO DT_ANY_INST_HAS_PROP_STATUS_OKAY(status_gpios)
19+
20+
LOG_MODULE_REGISTER(mb7040, CONFIG_SENSOR_LOG_LEVEL);
21+
22+
struct mb7040_data {
23+
uint16_t distance_cm;
24+
struct k_sem read_sem;
25+
#if MB7040_HAS_STATUS_GPIO
26+
struct gpio_callback gpio_cb;
27+
#endif
28+
};
29+
30+
struct mb7040_config {
31+
struct i2c_dt_spec i2c;
32+
uint8_t i2c_addr;
33+
#if MB7040_HAS_STATUS_GPIO
34+
struct gpio_dt_spec status_gpio;
35+
#endif
36+
};
37+
38+
#if MB7040_HAS_STATUS_GPIO
39+
static void status_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
40+
{
41+
struct mb7040_data *data = CONTAINER_OF(cb, struct mb7040_data, gpio_cb);
42+
43+
k_sem_give(&data->read_sem);
44+
}
45+
#endif
46+
47+
static int mb7040_sample_fetch(const struct device *dev, enum sensor_channel chan)
48+
{
49+
const struct mb7040_config *cfg = (struct mb7040_config *)dev->config;
50+
struct mb7040_data *data = (struct mb7040_data *)dev->data;
51+
uint8_t cmd = RANGE_CMD;
52+
int ret;
53+
uint8_t read_data[2];
54+
55+
if (chan != SENSOR_CHAN_DISTANCE && chan != SENSOR_CHAN_ALL) {
56+
LOG_ERR("Sensor only supports distance");
57+
return -EINVAL;
58+
}
59+
60+
k_sem_reset(&data->read_sem);
61+
62+
#if MB7040_HAS_STATUS_GPIO
63+
/* Check if status_gpio port is present */
64+
if (cfg->status_gpio.port != NULL) {
65+
/* Enable interrupt before writing */
66+
ret = gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_EDGE_FALLING);
67+
if (ret != 0) {
68+
LOG_ERR("Failed to configure interrupt: %d", ret);
69+
return ret;
70+
}
71+
}
72+
#endif
73+
74+
/* Write range command to sensor */
75+
ret = i2c_reg_write_byte_dt(&cfg->i2c, cfg->i2c_addr, cmd);
76+
77+
if (ret != 0) {
78+
LOG_ERR("I2C write failed with error %d", ret);
79+
#if MB7040_HAS_STATUS_GPIO
80+
/* Disable interrupt before returning error*/
81+
if (cfg->status_gpio.port != NULL) {
82+
gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_DISABLE);
83+
}
84+
#endif
85+
return ret;
86+
}
87+
88+
ret = k_sem_take(&data->read_sem, K_MSEC(CONFIG_MB7040_DELAY_MS));
89+
90+
#if MB7040_HAS_STATUS_GPIO
91+
/* Check if status_gpio port is present */
92+
if (cfg->status_gpio.port != NULL) {
93+
gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_DISABLE);
94+
if (ret != 0) {
95+
/* Success: interrupt fired and semaphore taken */
96+
return ret;
97+
}
98+
} else {
99+
if (ret != -EAGAIN) {
100+
/* Should not happen: semaphore only given by GPIO interrupt */
101+
return ret;
102+
}
103+
}
104+
#else
105+
if (ret != -EAGAIN) {
106+
/* Should not happen: semaphore only given by GPIO interrupt */
107+
return ret;
108+
}
109+
#endif
110+
/*
111+
* Small wait due to device specific internal i2c timings. 10ms is a common
112+
* wait to time to ensure ultrasonic sensors achieve stability and accuracy.
113+
*/
114+
k_msleep(10);
115+
116+
ret = i2c_read_dt(&cfg->i2c, read_data, 2);
117+
118+
if (ret != 0) {
119+
LOG_ERR("I2C read failed with error %d", ret);
120+
#if MB7040_HAS_STATUS_GPIO
121+
if (cfg->status_gpio.port != NULL) {
122+
gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_DISABLE);
123+
}
124+
#endif
125+
return ret;
126+
}
127+
128+
/* Convert MSB/LSB to distance in cm */
129+
data->distance_cm = (read_data[0] << 8) | read_data[1];
130+
return 0;
131+
}
132+
133+
static int mb7040_channel_get(const struct device *dev, enum sensor_channel chan,
134+
struct sensor_value *val)
135+
{
136+
struct mb7040_data *data = (struct mb7040_data *)dev->data;
137+
138+
if (chan != SENSOR_CHAN_DISTANCE) {
139+
LOG_ERR("Sensor only supports distance");
140+
return -ENOTSUP;
141+
}
142+
143+
/* Meters */
144+
val->val1 = data->distance_cm / 100;
145+
/* Micrometers */
146+
val->val2 = (data->distance_cm % 100) * 10000;
147+
148+
return 0;
149+
}
150+
151+
static DEVICE_API(sensor, mb7040_api) = {
152+
.sample_fetch = mb7040_sample_fetch,
153+
.channel_get = mb7040_channel_get,
154+
};
155+
156+
static int mb7040_init(const struct device *dev)
157+
{
158+
const struct mb7040_config *cfg = (struct mb7040_config *)dev->config;
159+
struct mb7040_data *data = (struct mb7040_data *)dev->data;
160+
161+
k_sem_init(&data->read_sem, 0, 1);
162+
163+
if (!i2c_is_ready_dt(&cfg->i2c)) {
164+
LOG_ERR("I2C not ready!");
165+
return -ENODEV;
166+
}
167+
/* Initialize status GPIO if present */
168+
#if MB7040_HAS_STATUS_GPIO
169+
if (cfg->status_gpio.port != NULL) {
170+
if (!gpio_is_ready_dt(&cfg->status_gpio)) {
171+
LOG_ERR("Status GPIO not ready");
172+
return -ENODEV;
173+
}
174+
175+
int ret = gpio_pin_configure_dt(&cfg->status_gpio, GPIO_INPUT);
176+
177+
if (ret < 0) {
178+
LOG_ERR("Failed to configure status GPIO: %d", ret);
179+
return ret;
180+
}
181+
182+
gpio_init_callback(&data->gpio_cb, status_gpio_callback, BIT(cfg->status_gpio.pin));
183+
ret = gpio_add_callback(cfg->status_gpio.port, &data->gpio_cb);
184+
if (ret < 0) {
185+
LOG_ERR("Failed to add GPIO callback: %d", ret);
186+
return ret;
187+
}
188+
LOG_INF("MB7040 initialized with status GPIO");
189+
}
190+
#else
191+
LOG_INF("MB7040 initialized");
192+
#endif
193+
return 0;
194+
}
195+
196+
#define MB7040_DEFINE(inst) \
197+
static struct mb7040_data mb7040_data_##inst = { \
198+
.distance_cm = 0, \
199+
}; \
200+
static const struct mb7040_config mb7040_config_##inst = { \
201+
.i2c = I2C_DT_SPEC_INST_GET(inst), \
202+
.i2c_addr = DT_INST_REG_ADDR(inst), \
203+
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, status_gpios), \
204+
(.status_gpio = GPIO_DT_SPEC_INST_GET(inst, status_gpios),)) }; \
205+
SENSOR_DEVICE_DT_INST_DEFINE(inst, mb7040_init, NULL, &mb7040_data_##inst, \
206+
&mb7040_config_##inst, POST_KERNEL, \
207+
CONFIG_SENSOR_INIT_PRIORITY, &mb7040_api);
208+
209+
DT_INST_FOREACH_STATUS_OKAY(MB7040_DEFINE)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
description: MB7040 ultrasonic distance sensor
2+
3+
compatible: "maxbotix,mb7040"
4+
5+
include: [sensor-device.yaml, i2c-device.yaml]
6+
7+
properties:
8+
status-gpios:
9+
type: phandle-array
10+
description: |
11+
GPIO pin connected to Pin 2 (Address Announce/Status) to monitor sensor
12+
state. Pin is high during range reading and low when ready for I2C communication.

tests/drivers/build_all/sensor/i2c.dtsi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,3 +1382,9 @@ test_i2c_lsm9ds1_mag: lsm9ds1_mag@b9 {
13821382
compatible = "st,lsm9ds1_mag";
13831383
reg = <0xb9>;
13841384
};
1385+
1386+
test_i2c_mb7040: mb7040@ba {
1387+
compatible = "maxbotix,mb7040";
1388+
reg = <0xba>;
1389+
status-gpios = <&test_gpio 0 0>;
1390+
};

0 commit comments

Comments
 (0)