Skip to content

drivers: sensor: Add support for BH1730 ambient light sensor #89908

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/sensor/rohm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@

# zephyr-keep-sorted-start
add_subdirectory_ifdef(CONFIG_BD8LB600FS_DIAGNOSTICS bd8lb600fs)
add_subdirectory_ifdef(CONFIG_BH1730 bh1730)
add_subdirectory_ifdef(CONFIG_BH1750 bh1750)
# zephyr-keep-sorted-stop
1 change: 1 addition & 0 deletions drivers/sensor/rohm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@

# zephyr-keep-sorted-start
source "drivers/sensor/rohm/bd8lb600fs/Kconfig"
source "drivers/sensor/rohm/bh1730/Kconfig"
source "drivers/sensor/rohm/bh1750/Kconfig"
# zephyr-keep-sorted-stop
5 changes: 5 additions & 0 deletions drivers/sensor/rohm/bh1730/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0

zephyr_library()

zephyr_library_sources(bh1730.c)
12 changes: 12 additions & 0 deletions drivers/sensor/rohm/bh1730/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# BH1730 ambient light sensor configuration options

# Copyright (c) 2025, Borislav Kereziev
# SPDX-License-Identifier: Apache-2.0

config BH1730
bool "BH1730 Ambient Light Sensor"
default y
depends on DT_HAS_ROHM_BH1730_ENABLED
select I2C
help
Enable driver for BH1730 ambient light sensor.
306 changes: 306 additions & 0 deletions drivers/sensor/rohm/bh1730/bh1730.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
/*
* Copyright (c) 2025, Borislav Kereziev
*
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT rohm_bh1730

#include <errno.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(BH1730, CONFIG_SENSOR_LOG_LEVEL);

#define BH1730_REG_CONTROL 0x00
#define BH1730_REG_TIMING 0x01
#define BH1730_REG_GAIN 0x07
#define BH1730_REG_ID 0x12
#define BH1730_REG_DATA0LOW 0x14
#define BH1730_REG_DATA0HIGH 0x15
#define BH1730_REG_DATA1LOW 0x16
#define BH1730_REG_DATA1HIGH 0x17

#define BH1730_PART_ID 0x71
#define BH1730_GAIN_DEFAULT 0x0
#define BH1730_ITIME_DEFAULT 0xDA
#define BH1730_CONTROL_ADC_EN_POWER_ON_SINGLE_READING 0x0B
#define BH1730_CONTROL_ADC_EN_POWER_ON 0x03
#define BH1730_CONTROL_ADC_DATA_UPDATED (1 << 4)
#define BH1730_TINT 2.8E-9 /* Internal clock period Tint */

#define BH1730_CMD 0x80 /* Command register CMD bit */
#define BH1730_CMD_ADDR_MASK 0x1F

/* Data definitions */

struct bh1730_data {
uint16_t data0; /* visible light */
uint16_t data1; /* infrared light */
};

struct bh1730_config {
struct i2c_dt_spec i2c;
uint8_t gain;
uint8_t itime;
};

/* Prototypes */

static int bh1730_reg_read_8(const struct device *dev, uint8_t reg, uint8_t *val);
static int bh1730_reg_write_8(const struct device *dev, uint8_t reg, uint8_t val);
static int bh1730_data_read(const struct device *dev, uint16_t *data0, uint16_t *data1);
static uint32_t bh1730_get_integration_time_ms(uint8_t itime_reg);
static uint8_t bh1730_get_gain(uint8_t gain_reg_val);
static uint32_t bh1730_calculate_lux(uint16_t data0, uint16_t data1, uint8_t itime_reg,
uint8_t gain_reg);
static int bh1730_sample_fetch(const struct device *dev, enum sensor_channel chan);
static int bh1730_sample_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val);
static int bh1730_init(const struct device *dev);

/* Functions */

static int bh1730_reg_read_8(const struct device *dev, uint8_t reg, uint8_t *val)
{
const struct bh1730_config *cfg = dev->config;
uint8_t cmd = BH1730_CMD | (reg & BH1730_CMD_ADDR_MASK);

return i2c_write_read_dt(&cfg->i2c, &cmd, sizeof(cmd), val, sizeof(*val));
}

static int bh1730_reg_write_8(const struct device *dev, uint8_t reg, uint8_t val)
{
const struct bh1730_config *cfg = dev->config;
uint8_t cmd = BH1730_CMD | (reg & BH1730_CMD_ADDR_MASK);
uint8_t buf[2] = {cmd, val};

return i2c_write_dt(&cfg->i2c, buf, sizeof(buf));
}

static int bh1730_data_read(const struct device *dev, uint16_t *data0, uint16_t *data1)
{
const struct bh1730_config *cfg = dev->config;

/* Ensure data has been updated */
uint8_t control_reg = 0x0;
int err = bh1730_reg_read_8(dev, BH1730_REG_CONTROL, &control_reg);

if (err) {
LOG_ERR("Failed reading CONTROL register");
return -EIO;
}
if ((control_reg & BH1730_CONTROL_ADC_DATA_UPDATED) == 0) {
LOG_ERR("Data not updated");
return -ENODATA;
}

/* Read data0 and data1 bytes */
uint8_t buffer[4] = {0};
uint8_t cmd = BH1730_CMD | BH1730_REG_DATA0LOW;

err = i2c_write_read_dt(&cfg->i2c, &cmd, sizeof(cmd), &buffer, sizeof(buffer));
if (err) {
return -EIO;
}

/* Data is shifted LSB first from sensor - swap */
*data0 = (buffer[1] << 8 | buffer[0]);
*data1 = (buffer[3] << 8 | buffer[2]);

return 0;
}

static inline uint32_t bh1730_get_integration_time_ms(uint8_t itime_reg)
{
/* Convert register to integration time in ms */
const uint32_t K_scaled = 2699200; /* 2.8e-6 * 964 * 1000 * 1e6 */
uint32_t integration_time_ms;

integration_time_ms = (K_scaled * (256 - itime_reg)) / 1000000;

return integration_time_ms;
}

static inline uint8_t bh1730_get_gain(uint8_t gain_reg_val)
{
/* Convert register value to gain */
if (gain_reg_val == 0x0) {
return 1;
}
if (gain_reg_val == 0x01) {
return 2;
}
if (gain_reg_val == 0x02) {
return 64;
}
if (gain_reg_val == 0x03) {
return 128;
}

return 1;
}

static uint32_t bh1730_calculate_lux(uint16_t data0, uint16_t data1, uint8_t itime_reg,
uint8_t gain_reg)
{
/* Check page 13 of datasheet for lux calculation formula */
const uint32_t SCALE = 1000;
const uint32_t scale1026 = 102600; /* 102.6 * 1000 */

/* prevent division by zero */
if (data0 == 0) {
return 0;
}

uint32_t itime_ms = bh1730_get_integration_time_ms(itime_reg);
uint8_t gain = bh1730_get_gain(gain_reg);

/* Compute ratio = (DATA1 / DATA0) scaled by 1000 */
uint32_t ratio = ((uint32_t)data1 * SCALE) / data0;
uint32_t k0_scaled, k1_scaled;

if (ratio < 260) {
k0_scaled = 1290;
k1_scaled = 2733;
} else if (ratio < 550) {
k0_scaled = 795;
k1_scaled = 859;
} else if (ratio < 1090) {
k0_scaled = 510;
k1_scaled = 345;
} else if (ratio < 2130) {
k0_scaled = 276;
k1_scaled = 130;
} else {
return 0;
}

/* Compute numerator (scaled values, may be negative → use int64_t) */
int64_t numerator = (int64_t)data0 * k0_scaled - (int64_t)data1 * k1_scaled;
uint64_t denominator = (uint64_t)gain * scale1026;

if (numerator <= 0 || denominator == 0) {
return 0;
}

uint32_t lux = (uint32_t)((numerator * itime_ms) / denominator);

return lux;
}

static int bh1730_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct bh1730_data *data = dev->data;
const struct bh1730_config *cfg = dev->config;

if ((chan != SENSOR_CHAN_ALL) && (chan != SENSOR_CHAN_LIGHT)) {
LOG_ERR("Unsupported channel %d", chan);
return -ENOTSUP;
}

/* Trigger measurement */
int ret = bh1730_reg_write_8(dev, BH1730_REG_CONTROL,
BH1730_CONTROL_ADC_EN_POWER_ON_SINGLE_READING);
if (ret) {
LOG_ERR("Failed writing to CONTROL register");
return -EIO;
}

/* Wait for conversion */
int32_t sleep_period_ms = bh1730_get_integration_time_ms(cfg->itime);

k_msleep(sleep_period_ms);

/* Read conversion result from device */
ret = bh1730_data_read(dev, &data->data0, &data->data1);
if (ret) {
LOG_ERR("Failed reading data from sensor");
return -EIO;
}

return 0;
}

static int bh1730_sample_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct bh1730_data *data = dev->data;
const struct bh1730_config *cfg = dev->config;

if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_LIGHT) {
return -ENOTSUP;
}

uint32_t lux = bh1730_calculate_lux(data->data0, data->data1, cfg->itime, cfg->gain);

val->val1 = lux;
val->val2 = 0;

return 0;
}

static int bh1730_init(const struct device *dev)
{
const struct bh1730_config *cfg = dev->config;
int ret = 0;

LOG_DBG("Initializing");

if (!i2c_is_ready_dt(&cfg->i2c)) {
LOG_ERR("I2C device not ready");
return -ENODEV;
}

/* Ensure BH1730 has valid ID */
uint8_t val = 0;

ret = bh1730_reg_read_8(dev, BH1730_REG_ID, &val);
if (ret != 0) {
LOG_ERR("Failed reading ID reg");
return -ENODEV;
}

/* Part number in bits 7:4 */
if ((val >> 4) != (BH1730_PART_ID >> 4)) {
LOG_ERR("Part number does not match, received 0x%1X", val >> 4);
return -ENODEV;
}

/* Configure part with gain */
if (cfg->gain != BH1730_GAIN_DEFAULT) {
ret = bh1730_reg_write_8(dev, BH1730_REG_GAIN, cfg->gain);
if (ret) {
LOG_ERR("Failed writing to gain register");
return -EIO;
}
}

/* Configure part with itime */
if (cfg->itime != BH1730_ITIME_DEFAULT) {
ret = bh1730_reg_write_8(dev, BH1730_REG_TIMING, cfg->itime);
if (ret) {
LOG_ERR("Failed writing to ITIME register");
return -EIO;
}
}

return ret;
}

static DEVICE_API(sensor, bh1730_api) = {.sample_fetch = bh1730_sample_fetch,
.channel_get = bh1730_sample_get};

#define BH1730_DEFINE(inst) \
static struct bh1730_data bh1730_data_##inst; \
static const struct bh1730_config bh1730_config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
.gain = DT_INST_PROP(inst, gain), \
.itime = DT_INST_PROP(inst, itime), \
}; \
SENSOR_DEVICE_DT_INST_DEFINE(inst, bh1730_init, NULL, &bh1730_data_##inst, \
&bh1730_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &bh1730_api);

DT_INST_FOREACH_STATUS_OKAY(BH1730_DEFINE)
37 changes: 37 additions & 0 deletions dts/bindings/sensor/rohm,bh1730.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) 2025, Borislav Kereziev
# SPDX-License-Identifier: Apache-2.0

description: Rohm BH1730 ambient light sensor.

compatible: "rohm,bh1730"

include: [sensor-device.yaml, i2c-device.yaml]

properties:
gain:
type: int
default: 0
description: |
ADC resolution setting
0 = x1 gain mode
1 = x2 gain mode
2 = x64 gain mode
3 = x128 gain mode
Lower gain values (x1 and x2) reduce sensitivity and prevent
saturation in brighter environments.
Higher gain values (x64 and x128) result in higher sensitivity,
which are useful in low-light environments.
The default corresponds to the reset value of the GAIN register.
enum:
- 0
- 1
- 2
- 3
itime:
type: int
default: 0xDA
description: |
ITIME value in TIMING register determines integration time.
Higher values result in longer integration time and
increased accuracy.
The default corresponds to the reset value of the TIMING register.
5 changes: 5 additions & 0 deletions tests/drivers/build_all/sensor/i2c.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -1302,3 +1302,8 @@ test_i2c_rm3100: rm3100@b2 {
reg = <0xb2>;
int-gpios = <&test_gpio 0 0>;
};

test_i2c_bh1730: bh1730@b3 {
compatible = "rohm,bh1730";
reg = <0xb3>;
};