Initial Commit

This commit is contained in:
SSAnkur committed 2026-10-17 21:05:22 +06:00
commit a42a6db38b
5 files changed
+234

No files matched your search

+1
View File
@@ -0,0 +1 @@
build/
+4
View File
@@ -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)
+87
View File
@@ -0,0 +1,87 @@
/* Pro Micro nRF52840 (nice!nano v2 compatible) + Adafruit SHT41 on a gated supply */
#include <zephyr/dt-bindings/adc/adc.h>
#include <zephyr/dt-bindings/adc/nrf-saadc.h>
#include <zephyr/dt-bindings/gpio/nordic-nrf-gpio.h>
/ {
/* 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) */
&reg0 {
status = "okay";
};
&reg1 {
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
};
/* I2C: SDA P0.20, SCL P0.22. "sleep" state = pins disconnected (no back-feeding) */
&pinctrl {
sht_i2c_default: sht_i2c_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 20)>, <NRF_PSEL(TWIM_SCL, 0, 22)>;
bias-pull-up;
};
};
sht_i2c_sleep: sht_i2c_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 20)>, <NRF_PSEL(TWIM_SCL, 0, 22)>;
low-power-enable;
};
};
};
&i2c0 {
compatible = "nordic,nrf-twim";
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
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 = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_VDDHDIV5>;
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"; };
+22
View File
@@ -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
+120
View File
@@ -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 <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/sys/byteorder.h>
#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);
}
}
}