commit eb324c8c06c7eacfc2b6355d45d6ca7935c7556e Author: SSAnkur Date: Sat Oct 17 21:05:22 2026 +0600 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e3ddc5f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(nRF52840-SHT41-BTHome-NCS) +target_sources(app PRIVATE src/main.c) diff --git a/app.overlay b/app.overlay new file mode 100644 index 0000000..18757b0 --- /dev/null +++ b/app.overlay @@ -0,0 +1,87 @@ +/* Pro Micro nRF52840 (nice!nano v2 compatible) + Adafruit SHT41 on a gated supply */ +#include +#include +#include + +/ { + /* P0.17 powers the SHT41: switched on only while the sensor is in use */ + sens_pwr: sensor-power { + compatible = "power-domain-gpio"; + enable-gpios = <&gpio0 17 (GPIO_ACTIVE_HIGH | NRF_GPIO_DRIVE_H0H1)>; + startup-delay-us = <3000>; /* SHT41 power-up + breakout caps */ + #power-domain-cells = <0>; + zephyr,pm-device-runtime-auto; + }; + + zephyr,user { + io-channels = <&adc 0>; + }; +}; + +/* DC/DC on REG0 (VDDH -> VDD) and REG1 (VDD -> core) */ +®0 { + status = "okay"; +}; + +®1 { + regulator-initial-mode = ; +}; + +/* I2C: SDA P0.20, SCL P0.22. "sleep" state = pins disconnected (no back-feeding) */ +&pinctrl { + sht_i2c_default: sht_i2c_default { + group1 { + psels = , ; + bias-pull-up; + }; + }; + + sht_i2c_sleep: sht_i2c_sleep { + group1 { + psels = , ; + low-power-enable; + }; + }; +}; + +&i2c0 { + compatible = "nordic,nrf-twim"; + status = "okay"; + clock-frequency = ; + pinctrl-0 = <&sht_i2c_default>; + pinctrl-1 = <&sht_i2c_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,pm-device-runtime-auto; /* bus enabled only during transfers */ + + sht41: sht4x@44 { + compatible = "sensirion,sht4x"; + reg = <0x44>; + repeatability = <2>; /* high precision, no heater */ + power-domains = <&sens_pwr>; + zephyr,pm-device-runtime-auto; + }; +}; + +/* Battery: VDDH/5, internal 0.6 V ref, gain 1/2 -> 0..1.2 V range */ +&adc { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_2"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <14>; /* LSB = 6000 mV / 16384 = 0.37 mV at VDDH */ + zephyr,oversampling = <3>; /* 2^3 = 8x */ + }; +}; + +/* Unused board peripherals */ +&i2c1 { status = "disabled"; }; +&spi2 { status = "disabled"; }; +&pwm0 { status = "disabled"; }; +&uart0 { status = "disabled"; }; +&usbd { status = "disabled"; }; diff --git a/prj.conf b/prj.conf new file mode 100644 index 0000000..d8cf201 --- /dev/null +++ b/prj.conf @@ -0,0 +1,22 @@ +# Bluetooth: advertising only +CONFIG_BT=y +CONFIG_BT_BROADCASTER=y +CONFIG_BT_DEVICE_NAME="SHT41" +CONFIG_BT_CTLR_TX_PWR_0=y + +# Drivers +CONFIG_I2C=y +CONFIG_SENSOR=y +CONFIG_ADC=y + +# Power management: sensor + I2C bus powered only while in use +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y +CONFIG_POWER_DOMAIN=y + +# Board enables USB serial console by default: turn it all off +CONFIG_USB_DEVICE_STACK_NEXT=n +CONFIG_SERIAL=n +CONFIG_CONSOLE=n +CONFIG_UART_CONSOLE=n +CONFIG_LOG=n diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..bd2d077 --- /dev/null +++ b/src/main.c @@ -0,0 +1,120 @@ +/* + * BTHome v2 temperature / humidity beacon - ultra-low-power (Zephyr) + * + * Every 60 s: power up SHT41, measure T/RH, power it down, read VDDH, + * update a non-connectable BTHome v2 advertisement sent every 10 s. + * Sensor power gating, I2C pin handling, DC/DC and sleep are all + * configured in app.overlay / prj.conf. + */ +#include +#include +#include +#include +#include +#include + +#define MEAS_INTERVAL K_SECONDS(60) +#define ADV_INTERVAL 16000 /* 10000 ms / 0.625 ms (legacy max is 10.24 s) */ +#define VDDH_DIV 5 /* SAADC input is VDDH/5 */ + +static const struct device *const sht = DEVICE_DT_GET(DT_NODELABEL(sht41)); +static const struct adc_dt_spec vbat = ADC_DT_SPEC_GET(DT_PATH(zephyr_user)); + +/* BTHome v2 service data (objects in ascending ID order: 0x0C, 0x2E, 0x45) */ +static uint8_t svc[] = { + 0xD2, 0xFC, /* UUID 0xFCD2 */ + 0x40, /* BTHome v2, unencrypted, regular interval */ + 0x0C, 0, 0, /* voltage, 1 mV */ + 0x2E, 0, /* humidity, 1 % */ + 0x45, 0, 0, /* temperature, 0.1 degC */ +}; + +static const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR), + BT_DATA(BT_DATA_SVC_DATA16, svc, sizeof(svc)), + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), +}; + +static int read_sht41(void) +{ + struct sensor_value t, rh; + int err = pm_device_runtime_get(sht); /* sensor power on */ + + if (err) { + return err; + } + err = sensor_sample_fetch(sht); + pm_device_runtime_put(sht); /* bus high-Z, then sensor power off */ + if (err) { + return err; + } + + sensor_channel_get(sht, SENSOR_CHAN_AMBIENT_TEMP, &t); + sensor_channel_get(sht, SENSOR_CHAN_HUMIDITY, &rh); + + int32_t mc = sensor_value_to_milli(&t); + int16_t t10 = (mc + (mc < 0 ? -50 : 50)) / 100; + int32_t pct = (sensor_value_to_milli(&rh) + 500) / 1000; + + sys_put_le16(t10, &svc[9]); + svc[7] = CLAMP(pct, 0, 100); + return 0; +} + +static void read_battery(void) +{ + static bool calibrated; + int16_t raw; + struct adc_sequence seq = { + .buffer = &raw, + .buffer_size = sizeof(raw), + .calibrate = !calibrated, + }; + + adc_sequence_init_dt(&vbat, &seq); + if (adc_read_dt(&vbat, &seq) == 0) { + /* Full-scale input voltage: 0.6 V ref / gain 1/2 = 1200 mV */ + int32_t full_mv = adc_ref_internal(vbat.dev); + + adc_gain_invert(vbat.channel_cfg.gain, &full_mv); + + /* + * Scale raw straight to VDDH in one step and round to nearest: + * VDDH_mV = raw * 1200 * 5 / 2^res. Multiplying by 5 before the + * division keeps 1 mV resolution instead of 5 mV steps. + * Max 16383 * 1200 * 5 = 98 M, fits in 32 bits. + */ + uint32_t num = (uint32_t)MAX(raw, 0) * (uint32_t)full_mv * VDDH_DIV; + uint32_t mv = (num + BIT(vbat.resolution - 1)) >> vbat.resolution; + + sys_put_le16(mv, &svc[4]); + calibrated = true; + } +} + +static bool measure(void) +{ + if (read_sht41()) { + return false; /* keep advertising last good values */ + } + read_battery(); /* right after sensor load */ + return true; +} + +int main(void) +{ + adc_channel_setup_dt(&vbat); + bt_enable(NULL); + measure(); /* advertise even if the first read fails */ + + bt_le_adv_start(BT_LE_ADV_PARAM(BT_LE_ADV_OPT_USE_IDENTITY, ADV_INTERVAL, + ADV_INTERVAL, NULL), + ad, ARRAY_SIZE(ad), NULL, 0); + + for (;;) { + k_sleep(MEAS_INTERVAL); /* CPU in System ON idle */ + if (measure()) { + bt_le_adv_update_data(ad, ARRAY_SIZE(ad), NULL, 0); + } + } +}