Initial Commit
This commit is contained in:
commit
a42a6db38b
5 files changed
+234
No files matched your search
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -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
@@ -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) */
|
||||
®0 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
®1 {
|
||||
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"; };
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user