Skip to content

Commit c75d378

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 1207880 commit c75d378

File tree

5 files changed

+224
-0
lines changed

5 files changed

+224
-0
lines changed

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 measurment"
16+
default 100
17+
depends on MB7040
18+
help
19+
Default delay if status-gpio is not defined

drivers/sensor/mb7040/mb7040.c

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
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/devicetree.h>
14+
#include <zephyr/logging/log.h>
15+
#include <zephyr/drivers/gpio.h>
16+
17+
#define RANGE_CMD 0x51
18+
#define MB7040_HAS_STATUS_GPIO DT_ANY_INST_HAS_PROP_STATUS_OKAY(status_gpios)
19+
20+
LOG_MODULE_REGISTER(mb7040, LOG_LEVEL_DBG);
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+
#if MB7040_HAS_STATUS_GPIO
33+
struct gpio_dt_spec status_gpio;
34+
#endif
35+
};
36+
37+
#if MB7040_HAS_STATUS_GPIO
38+
static void status_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
39+
{
40+
struct mb7040_data *data = CONTAINER_OF(cb, struct mb7040_data, gpio_cb);
41+
k_sem_give(&data->read_sem);
42+
}
43+
#endif
44+
45+
static int mb7040_sample_fetch(const struct device *dev, enum sensor_channel chan)
46+
{
47+
const struct mb7040_config *cfg = (struct mb7040_config *)dev->config;
48+
struct mb7040_data *data = (struct mb7040_data *)dev->data;
49+
uint8_t cmd = RANGE_CMD;
50+
int ret;
51+
uint8_t read_data[2];
52+
53+
if (chan != SENSOR_CHAN_DISTANCE && chan != SENSOR_CHAN_ALL) {
54+
LOG_ERR("Sensor only supports distance");
55+
return -EINVAL;
56+
}
57+
58+
k_sem_reset(&data->read_sem);
59+
60+
#if MB7040_HAS_STATUS_GPIO
61+
/* Enable interrupt before writing */
62+
ret = gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_EDGE_FALLING);
63+
if (ret != 0) {
64+
LOG_ERR("Failed to configure interrupt: %d", ret);
65+
return ret;
66+
}
67+
#endif
68+
69+
/* Write range command to sensor */
70+
ret = i2c_write_dt(&cfg->i2c, &cmd, 1);
71+
if (ret != 0) {
72+
LOG_ERR("I2C write failed with error %d", ret);
73+
return ret;
74+
}
75+
76+
ret = k_sem_take(&data->read_sem, K_MSEC(CONFIG_MB7040_DELAY_MS));
77+
if (ret != 0) {
78+
if (ret == -EAGAIN) {
79+
LOG_ERR("Semaphore take timed out");
80+
return -ETIMEDOUT;
81+
} else {
82+
LOG_ERR("Semaphore take failed with error %d", ret);
83+
return ret;
84+
}
85+
}
86+
87+
#if MB7040_HAS_STATUS_GPIO
88+
gpio_pin_interrupt_configure_dt(&cfg->status_gpio, GPIO_INT_DISABLE);
89+
#endif
90+
91+
/* Small wait due to devie specific i2c timings */
92+
k_msleep(10);
93+
ret = i2c_read_dt(&cfg->i2c, read_data, 2);
94+
if (ret != 0) {
95+
LOG_ERR("I2C read failed with error %d", ret);
96+
return ret;
97+
}
98+
99+
/* Convert MSB/LSB to distance in cm */
100+
data->distance_cm = (read_data[0] << 8) | read_data[1];
101+
return 0;
102+
}
103+
104+
static int mb7040_channel_get(const struct device *dev, enum sensor_channel chan,
105+
struct sensor_value *val)
106+
{
107+
struct mb7040_data *data = (struct mb7040_data *)dev->data;
108+
109+
if (chan != SENSOR_CHAN_DISTANCE && SENSOR_CHAN_ALL != chan) {
110+
LOG_ERR("Sensor only supports distance");
111+
return -EINVAL;
112+
}
113+
114+
val->val1 = data->distance_cm;
115+
val->val2 = 0;
116+
return 0;
117+
}
118+
119+
static const struct sensor_driver_api mb7040_api = {
120+
.sample_fetch = mb7040_sample_fetch,
121+
.channel_get = mb7040_channel_get,
122+
};
123+
124+
static int mb7040_init(const struct device *dev)
125+
{
126+
const struct mb7040_config *cfg = (struct mb7040_config *)dev->config;
127+
struct mb7040_data *data = (struct mb7040_data *)dev->data;
128+
129+
k_sem_init(&data->read_sem, 0, 1);
130+
131+
if (!i2c_is_ready_dt(&cfg->i2c)) {
132+
LOG_ERR("I2C not ready!");
133+
return -ENODEV;
134+
}
135+
136+
/* Initialize status GPIO if present */
137+
#if MB7040_HAS_STATUS_GPIO
138+
if (!gpio_is_ready_dt(&cfg->status_gpio)) {
139+
LOG_ERR("Status GPIO not ready");
140+
return -ENODEV;
141+
}
142+
143+
int ret = gpio_pin_configure_dt(&cfg->status_gpio, GPIO_INPUT);
144+
if (ret < 0) {
145+
LOG_ERR("Failed to configure status GPIO: %d", ret);
146+
return ret;
147+
}
148+
149+
gpio_init_callback(&data->gpio_cb, status_gpio_callback, BIT(cfg->status_gpio.pin));
150+
ret = gpio_add_callback(cfg->status_gpio.port, &data->gpio_cb);
151+
if (ret < 0) {
152+
LOG_ERR("Failed to add GPIO callback: %d", ret);
153+
return ret;
154+
}
155+
156+
LOG_INF("MB7040 initialized with status GPIO");
157+
#else
158+
LOG_INF("MB7040 initialized");
159+
#endif
160+
return 0;
161+
}
162+
163+
#define MB7040_DEFINE(inst) \
164+
static struct mb7040_data mb7040_data_##inst = { \
165+
.distance_cm = 0, \
166+
}; \
167+
\
168+
static const struct mb7040_config mb7040_config_##inst = { \
169+
.i2c = I2C_DT_SPEC_INST_GET(inst), \
170+
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, status_gpios), \
171+
(.status_gpio = GPIO_DT_SPEC_INST_GET(inst, status_gpios),)) }; \
172+
\
173+
SENSOR_DEVICE_DT_INST_DEFINE(inst, mb7040_init, NULL, &mb7040_data_##inst, \
174+
&mb7040_config_##inst, POST_KERNEL, \
175+
CONFIG_SENSOR_INIT_PRIORITY, &mb7040_api);
176+
177+
DT_INST_FOREACH_STATUS_OKAY(MB7040_DEFINE)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
description: MB7040 ultrasonic distance sensor
2+
3+
compatible: "maxbotix,mb7040"
4+
5+
include: [sensor-device.yaml, i2c-device.yaml]
6+
7+
on-bus: i2c
8+
9+
properties:
10+
reg:
11+
description: I2C address of the MB7040 sensor. Zephyr expects 7-bit addressing and handles shifting internally.
12+
required: true
13+
14+
status-gpios:
15+
type: phandle-array
16+
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.
17+
required: false
18+

tests/drivers/build_all/sensor/i2c.dtsi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,3 +1331,8 @@ test_i2c_bh1730: bh1730@b4 {
13311331
compatible = "rohm,bh1730";
13321332
reg = <0xb4>;
13331333
};
1334+
1335+
test_i2c_mb7040: mb7040@70 {
1336+
compatible = "maxbotix,mb7040";
1337+
reg = <0xb5>;
1338+
};

0 commit comments

Comments
 (0)