Initial Commit

This commit is contained in:
SSAnkur committed 2026-10-20 15:33:46 +06:00
commit 9825b9b4f8
15 files changed
+1439

No files matched your search

+97
View File
@@ -0,0 +1,97 @@
/*
* Board / build configuration: Xiaomi LYWSD03MMC, hardware B1.5 ONLY
* as sold since 2025 (pvvx reports it as HW B1.1): TLSR8251F512ET24,
* SHTV3 sensor, SPI-driven LCD controller
*
* Pin map (from pvvx/ATC_MiThermometer reverse engineering):
* PC2 I2C SCL (SHTV3 sensor, 0x70), 10k pull-up
* PC3 I2C SDA, 10k pull-up
* PB6 LCD controller pin, must be pulled up (10k)
* PB0 not bonded on this package: driven high and sampled by the ADC
* to measure VDD (= CR2032 voltage)
* PD7 LCD SPI-CLK
* PB7 LCD SPI-SDI
*/
#pragma once
#define CLOCK_SYS_CLOCK_HZ 24000000
#define SYS_CLK_TYPE SYS_CLK_24M_Crystal
enum {
CLOCK_SYS_CLOCK_1S = CLOCK_SYS_CLOCK_HZ,
CLOCK_SYS_CLOCK_1MS = (CLOCK_SYS_CLOCK_1S / 1000),
CLOCK_SYS_CLOCK_1US = (CLOCK_SYS_CLOCK_1S / 1000000),
};
/* Low-power busy wait: CPU stalled, woken by timer0 */
#define pm_wait_us(t) cpu_stall_wakeup_by_timer0((t) * CLOCK_SYS_CLOCK_1US)
#define pm_wait_ms(t) cpu_stall_wakeup_by_timer0((t) * CLOCK_SYS_CLOCK_1MS)
/* Variables that must survive deep-sleep retention */
#define RAM _attribute_data_retention_
/* ---- Application settings (equivalents of the Zephyr build) ---- */
#define DEV_NAME "LYWSD03MMC" /* CONFIG_BT_DEVICE_NAME */
#define ADV_INTERVAL 16000 /* 10000 ms / 0.625 ms */
#define ADV_INTERVAL_DELAY 10 /* + up to 6.25 ms, as pvvx */
#define MEAS_EVERY_N_ADV 6 /* measure every 6th adv = 60 s */
#define MEAS_INTERVAL_S ((ADV_INTERVAL * MEAS_EVERY_N_ADV) / 1600)
#define RF_TX_POWER RF_POWER_P0p04dBm /* CONFIG_BT_CTLR_TX_PWR_0 */
#define LOW_BATT_MV 2400 /* show battery icon below this */
/* Local name in the advertising packet as well as the scan response.
* 1: passive scanners (e.g. ESPHome with active: false) see the name.
* 0 (pvvx default): name only in the scan response; every advertising
* packet is 12 bytes shorter, but only active scanners see the name. */
#define ADV_NAME_IN_ADV 1
/* ---- OTA window ----
* Connections (and with them OTA) are only accepted for OTA_WINDOW_S after
* the battery is inserted. Then the device switches to non-connectable
* advertising until the next power cycle. A reboot without a power cycle
* (low-battery sleep, OTA reboot, crash) does not reopen the window. */
#define OTA_WINDOW_S 120
#define OTA_WINDOW_ADV_INTERVAL 1600 /* 1 s while the window is open */
/* ---- Connection / OTA (values from pvvx ble.h / app.c) ---- */
#define CONN_INTERVAL 16 /* 16 * 1.25 ms = 20 ms */
#define CONN_LATENCY 49 /* (49 + 1) * 20 ms = 1 s between radio events */
#define CONN_TIMEOUT 400 /* x10 ms = 4 s */
#define OTA_IDLE_TIMEOUT_US (16 * 1000000) /* abort OTA after 16 s without data */
/* ---- I2C ---- */
#define I2C_GROUP I2C_GPIO_GROUP_C2C3
#define I2C_SCL GPIO_PC2
#define I2C_SDA GPIO_PC3
#define I2C_SPEED_HZ 700000 /* as pvvx I2C_MAX_SPEED; SHTC3 allows 1 MHz */
#define PULL_I2C PM_PIN_PULLUP_10K
/* ---- Battery ADC ---- */
#define GPIO_VBAT GPIO_PB0
#define ADC_VBAT_CHN B0P
#define ADC_VREF_MV 1175 /* typical internal 1.2 V ref, mV */
/* PB0 (not bonded) is driven high permanently, as in pvvx: no toggling
* around each ADC read, and the pad never floats. Applied by gpio_init()
* on every wake-up, including from deep-sleep retention. */
#define PB0_INPUT_ENABLE 0
#define PB0_OUTPUT_ENABLE 1
#define PB0_DATA_OUT 1
#define PB0_FUNC AS_GPIO
/* ---- LCD ---- */
#define GPIO_LCD_PB6 GPIO_PB6
#define PULL_LCD_PB6 PM_PIN_PULLUP_10K
#define GPIO_LCD_CLK GPIO_PD7 /* SPI clock to the LCD controller */
#define GPIO_LCD_SDI GPIO_PB7 /* SPI data to the LCD controller */
#define PULL_LCD_SER PM_PIN_PULLUP_1M
/* Unused "reset" pad: keep it from floating */
#define GPIO_KEY GPIO_PA5
#define PULL_KEY PM_PIN_PULLUP_1M
#define MODULE_WATCHDOG_ENABLE 0
/* Stock Xiaomi flash layout: factory MAC address lives here */
#define CFG_ADR_MAC 0x76000
#include "vendor/common/default_config.h"
+68
View File
@@ -0,0 +1,68 @@
/*
* VDD measurement. PB0 is not bonded out on the TLSR8251 package; it is
* kept driven high (= VDD) permanently by gpio_init() (see app_config.h,
* same as pvvx) and sampled through the 1/8 prescaler against the
* 1.2 V reference. 8 samples are taken, sorted, and the middle 4 averaged.
* ADC sequence follows pvvx/ATC_MiThermometer battery.c.
*/
#include "tl_common.h"
#include "drivers.h"
#include "app_config.h"
#include "battery.h"
#define ADC_SAMPLES 8
/* ADC analog config is not kept across deep-sleep retention: set it up each time */
static void vbat_adc_setup(void)
{
adc_power_on_sar_adc(0);
adc_set_sample_clk(5);
adc_set_left_right_gain_bias(GAIN_STAGE_BIAS_PER100, GAIN_STAGE_BIAS_PER100);
adc_set_chn_enable_and_max_state_cnt(ADC_MISC_CHN, 2);
adc_set_state_length(240, 0, 10);
analog_write(anareg_adc_res_m, RES14 | FLD_ADC_EN_DIFF_CHN_M);
adc_set_ain_chn_misc(ADC_VBAT_CHN, GND);
adc_set_ref_voltage(ADC_MISC_CHN, ADC_VREF_1P2V);
adc_set_tsample_cycle_chn_misc(SAMPLING_CYCLES_6);
adc_set_ain_pre_scaler(ADC_PRESCALER_1F8);
}
_attribute_ram_code_
u16 battery_read_mv(void)
{
volatile u32 dat[ADC_SAMPLES];
u16 s[ADC_SAMPLES];
u8 rp = 0;
vbat_adc_setup();
adc_power_on_sar_adc(1);
adc_reset_adc_module();
for (int i = 0; i < ADC_SAMPLES; i++) {
dat[i] = 0;
}
adc_config_misc_channel_buf((u16 *)dat, sizeof(dat));
dfifo_enable_dfifo2();
for (int i = 0; i < ADC_SAMPLES; i++) {
while (rp == reg_dfifo2_wptr) {
}
rp = reg_dfifo2_wptr;
/* 14-bit result, bit 13 = sign in differential mode */
u16 v = (dat[i] & BIT(13)) ? 0 : (dat[i] & 0x1FFF);
int j = i - 1;
while (j >= 0 && s[j] > v) { /* insertion sort */
s[j + 1] = s[j];
j--;
}
s[j + 1] = v;
}
dfifo_disable_dfifo2();
adc_power_on_sar_adc(0);
/* mV = avg/8192 * Vref * 8 = sum4 * Vref / 4096 */
u32 sum = s[2] + s[3] + s[4] + s[5];
return (u16)((sum * ADC_VREF_MV + 2048) >> 12);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "tl_common.h"
/* Low-battery protection, thresholds as pvvx (battery.h, CR2032 build):
* below BOOT_VBAT_MV at power-on the device sleeps again before touching
* flash or the radio; below END_VBAT_MV while running it shows "Lo" and
* sleeps for LOW_VBAT_SLEEP_S, then retries. */
#define BOOT_VBAT_MV 2200
#define END_VBAT_MV 2000
#define LOW_VBAT_SLEEP_S 180
u16 battery_read_mv(void); /* VDD (= CR2032) in mV */
+340
View File
@@ -0,0 +1,340 @@
/*
* BLE peripheral: connectable advertising, GATT server with the Telink OTA
* service, and the connection / OTA power handling of
* pvvx/ATC_MiThermometer (ble.c, app_att.c), MIT-style license:
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software ... to deal in the Software without restriction.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*
* OTA itself (packet handling, CRC, flash writes, swapping the boot bank
* and rebooting) is done by the Telink BLE library: otaWrite()/otaRead().
* The new image is written to the bank that is not running (0x00000 or
* 0x20000, 128 KiB each), so a failed transfer leaves the old firmware
* bootable.
*/
#include "tl_common.h"
#include "drivers.h"
#include "stack/ble/ble.h"
#include "stack/ble/service/ble_ll_ota.h"
#include "vendor/common/blt_common.h"
#include "app_config.h"
#include "ble.h"
#include "lcd.h"
void bls_set_advertise_prepare(void *p); /* in the library, no header */
/* Peripheral Preferred Connection Parameters (not in this SDK's headers) */
typedef struct {
u16 intervalMin;
u16 intervalMax;
u16 latency;
u16 timeout;
} gap_periConnectParams_t;
/* ----- link-layer buffers (required by the library; sizes as pvvx) ----- */
RAM u8 blt_rxfifo_b[64 * 8] = { 0 };
RAM my_fifo_t blt_rxfifo = { 64, 8, 0, 0, blt_rxfifo_b, };
RAM u8 blt_txfifo_b[40 * 16] = { 0 };
RAM my_fifo_t blt_txfifo = { 40, 16, 0, 0, blt_txfifo_b, };
RAM u8 ble_connected;
RAM u8 ota_active;
RAM u8 ota_window;
RAM static u32 window_start;
#define OTA_WINDOW_TICKS ((u32)OTA_WINDOW_S * CLOCK_16M_SYS_TIMER_CLK_1S)
static void close_window(void);
RAM static u8 conn_param_accepted;
RAM static u8 mac_public[6];
static u8 mac_random_static[6];
/* Scan response: complete local name DEV_NAME */
#define NAME_LEN (sizeof(DEV_NAME) - 1)
RAM static u8 scan_rsp[2 + NAME_LEN];
/* ============================ GATT table ============================ */
enum {
ATT_H_START = 0,
GAP_PS_H, /* 0x1800 Generic Access */
GAP_DEVNAME_CD_H,
GAP_DEVNAME_DP_H,
GAP_APPEAR_CD_H,
GAP_APPEAR_DP_H,
GAP_CONNPAR_CD_H,
GAP_CONNPAR_DP_H,
GATT_PS_H, /* 0x1801 Generic Attribute */
GATT_SC_CD_H,
GATT_SC_DP_H,
GATT_SC_CCC_H,
OTA_PS_H, /* Telink OTA */
OTA_CD_H,
OTA_DP_H,
OTA_DESC_H,
ATT_END_H,
};
static const u16 primary_service_uuid = GATT_UUID_PRIMARY_SERVICE;
static const u16 character_uuid = GATT_UUID_CHARACTER;
static const u16 ccc_uuid = GATT_UUID_CLIENT_CHAR_CFG;
static const u16 user_desc_uuid = GATT_UUID_CHAR_USER_DESC;
/* GAP */
static const u16 gap_service_uuid = SERVICE_UUID_GENERIC_ACCESS;
static const u16 devname_uuid = GATT_UUID_DEVICE_NAME;
static const u16 appearance_uuid = GATT_UUID_APPEARANCE;
static const u16 connpar_uuid = GATT_UUID_PERI_CONN_PARAM;
static const u16 appearance = 768; /* Generic Thermometer */
static const gap_periConnectParams_t periConnParameters = {
CONN_INTERVAL, CONN_INTERVAL, CONN_LATENCY, CONN_TIMEOUT
};
static const u8 devname_cd[5] = {
CHAR_PROP_READ,
U16_LO(GAP_DEVNAME_DP_H), U16_HI(GAP_DEVNAME_DP_H),
U16_LO(GATT_UUID_DEVICE_NAME), U16_HI(GATT_UUID_DEVICE_NAME)
};
static const u8 appear_cd[5] = {
CHAR_PROP_READ,
U16_LO(GAP_APPEAR_DP_H), U16_HI(GAP_APPEAR_DP_H),
U16_LO(GATT_UUID_APPEARANCE), U16_HI(GATT_UUID_APPEARANCE)
};
static const u8 connpar_cd[5] = {
CHAR_PROP_READ,
U16_LO(GAP_CONNPAR_DP_H), U16_HI(GAP_CONNPAR_DP_H),
U16_LO(GATT_UUID_PERI_CONN_PARAM), U16_HI(GATT_UUID_PERI_CONN_PARAM)
};
/* GATT */
static const u16 gatt_service_uuid = SERVICE_UUID_GENERIC_ATTRIBUTE;
static const u16 service_change_uuid = GATT_UUID_SERVICE_CHANGE;
static u32 service_change_val;
RAM static u16 service_change_ccc;
static const u8 service_change_cd[5] = {
CHAR_PROP_INDICATE,
U16_LO(GATT_SC_DP_H), U16_HI(GATT_SC_DP_H),
U16_LO(GATT_UUID_SERVICE_CHANGE), U16_HI(GATT_UUID_SERVICE_CHANGE)
};
/* Telink OTA */
static const u8 ota_service_uuid[16] = TELINK_OTA_UUID_SERVICE;
static const u8 ota_data_uuid[16] = TELINK_SPP_DATA_OTA;
static u8 ota_data;
static const u8 ota_name[] = { 'O', 'T', 'A' };
static const u8 ota_cd[19] = {
CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RSP,
U16_LO(OTA_DP_H), U16_HI(OTA_DP_H),
0x12, 0x2B, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00
};
/* Make the library's OTA timeout an inactivity timeout (pvvx otaWritePre):
* every OTA packet restarts it. */
static int ota_write_pre(void *p)
{
blt_ota_start_tick = clock_time() | 1;
return otaWrite(p);
}
/* In RAM, as pvvx: the stack walks this table from interrupt context,
* which must not depend on flash reads while OTA is erasing/writing it. */
RAM static attribute_t attributes[] = {
{ ATT_END_H - 1, 0, 0, 0, 0, 0, 0, 0 }, /* total number of attributes */
{ 7, ATT_PERMISSIONS_READ, 2, 2, (u8 *)&primary_service_uuid, (u8 *)&gap_service_uuid, 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(devname_cd), (u8 *)&character_uuid, (u8 *)devname_cd, 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, NAME_LEN, (u8 *)&devname_uuid, &scan_rsp[2], 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(appear_cd), (u8 *)&character_uuid, (u8 *)appear_cd, 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(appearance), (u8 *)&appearance_uuid, (u8 *)&appearance, 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(connpar_cd), (u8 *)&character_uuid, (u8 *)connpar_cd, 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(periConnParameters), (u8 *)&connpar_uuid, (u8 *)&periConnParameters, 0, 0 },
{ 4, ATT_PERMISSIONS_READ, 2, 2, (u8 *)&primary_service_uuid, (u8 *)&gatt_service_uuid, 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(service_change_cd), (u8 *)&character_uuid, (u8 *)service_change_cd, 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(service_change_val), (u8 *)&service_change_uuid, (u8 *)&service_change_val, 0, 0 },
{ 0, ATT_PERMISSIONS_RDWR, 2, sizeof(service_change_ccc), (u8 *)&ccc_uuid, (u8 *)&service_change_ccc, 0, 0 },
{ 4, ATT_PERMISSIONS_READ, 2, 16, (u8 *)&primary_service_uuid, (u8 *)ota_service_uuid, 0, 0 },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(ota_cd), (u8 *)&character_uuid, (u8 *)ota_cd, 0, 0 },
{ 0, ATT_PERMISSIONS_RDWR, 16, sizeof(ota_data), (u8 *)ota_data_uuid, &ota_data, &ota_write_pre, &otaRead },
{ 0, ATT_PERMISSIONS_READ, 2, sizeof(ota_name), (u8 *)&user_desc_uuid, (u8 *)ota_name, 0, 0 },
};
/* ======================= connection / OTA events ======================= */
/* OTA start command received: full radio duty until it is done */
static void ota_start_cb(void)
{
ota_active = 1;
conn_param_accepted = 0;
bls_pm_setManualLatency(0);
bls_ota_setTimeout(OTA_IDLE_TIMEOUT_US);
}
/* After a successful OTA the library reboots into the new image itself;
* this only matters for failures. */
static void ota_result_cb(int result)
{
(void)result;
ota_active = 0;
}
/* Ask for 20 ms interval + latency 49: the device then only has to wake for
* one connection event per second while idle (pvvx ble_connect_callback). */
static void connect_cb(u8 e, u8 *p, int n)
{
(void)e; (void)p; (void)n;
ble_connected = 1;
conn_param_accepted = 0;
bls_l2cap_setMinimalUpdateReqSendingTime_after_connCreate(1000);
bls_l2cap_requestConnParamUpdate(CONN_INTERVAL, CONN_INTERVAL,
CONN_LATENCY, CONN_TIMEOUT);
lcd_show_ble(1);
}
static void disconnect_cb(u8 e, u8 *p, int n)
{
(void)e; (void)p; (void)n;
ble_connected = 0;
ota_active = 0;
lcd_show_ble(ota_window);
/* the stack resumes advertising by itself; ble_task() switches it to
* non-connectable if the window has expired meanwhile */
}
static int conn_param_rsp_cb(u8 id, u16 result)
{
(void)id;
if (result == CONN_PARAM_UPDATE_ACCEPT) {
conn_param_accepted = 1;
} else if (result == CONN_PARAM_UPDATE_REJECT) {
bls_l2cap_requestConnParamUpdate(CONN_INTERVAL, CONN_INTERVAL,
CONN_LATENCY, CONN_TIMEOUT);
}
return 0;
}
/* Suspend loses the RF power setting (pvvx suspend_exit_cb) */
_attribute_ram_code_
static void suspend_exit_cb(u8 e, u8 *p, int n)
{
(void)e; (void)p; (void)n;
rf_set_power_level_index(RF_TX_POWER);
}
/* ============================== init ============================== */
void ble_init(const u8 *adv, int adv_len, int (*adv_prepare)(void *),
int open_window)
{
/* Factory public address (A4:C1:38:...) from the stock flash layout */
blc_initMacAddress(CFG_ADR_MAC, mac_public, mac_random_static);
memcpy(&scan_rsp[2], DEV_NAME, NAME_LEN);
scan_rsp[0] = NAME_LEN + 1;
scan_rsp[1] = GAP_ADTYPE_LOCAL_NAME_COMPLETE;
/* Controller */
blc_ll_initStandby_module(mac_public);
blc_ll_initAdvertising_module(mac_public);
blc_ll_initConnection_module();
blc_ll_initSlaveRole_module();
/* Host */
blc_gap_peripheral_init();
bls_att_setAttributeTable((u8 *)attributes);
blc_l2cap_register_handler(blc_l2cap_packet_receive);
blc_smp_setSecurityLevel(No_Security);
rf_set_power_level_index(RF_TX_POWER);
bls_app_registerEventCallback(BLT_EV_FLAG_CONNECT, &connect_cb);
bls_app_registerEventCallback(BLT_EV_FLAG_TERMINATE, &disconnect_cb);
bls_app_registerEventCallback(BLT_EV_FLAG_SUSPEND_EXIT, &suspend_exit_cb);
blc_l2cap_registerConnUpdateRspCb(conn_param_rsp_cb);
/* OTA: erase the inactive bank if it still holds an old image */
bls_ota_clearNewFwDataArea();
bls_ota_registerStartCmdCb(ota_start_cb);
bls_ota_registerResultIndicateCb(ota_result_cb);
/* Advertising */
bls_ll_setAdvData((u8 *)adv, adv_len);
bls_ll_setScanRspData(scan_rsp, sizeof(scan_rsp));
bls_set_advertise_prepare((void *)adv_prepare);
if (open_window) {
/* 1 s interval so the flashing page finds and connects quickly */
ota_window = 1;
window_start = clock_time();
bls_ll_setAdvParam(OTA_WINDOW_ADV_INTERVAL,
OTA_WINDOW_ADV_INTERVAL + ADV_INTERVAL_DELAY,
ADV_TYPE_CONNECTABLE_UNDIRECTED, OWN_ADDRESS_PUBLIC,
0, NULL, BLT_ENABLE_ADV_ALL, ADV_FP_NONE);
bls_ll_setAdvEnable(BLC_ADV_ENABLE);
lcd_show_ble(1); /* BLE symbol = OTA window open */
} else {
close_window();
}
/* Power management (pvvx init_ble) */
blc_ll_initPowerManagement_module();
blc_pm_setDeepsleepRetentionThreshold(40, 18);
blc_pm_setDeepsleepRetentionEarlyWakeupTiming(240);
blc_pm_setDeepsleepRetentionType(DEEPSLEEP_MODE_RET_SRAM_LOW32K);
bls_pm_setSuspendMask(SUSPEND_ADV | DEEPSLEEP_RETENTION_ADV |
SUSPEND_CONN | DEEPSLEEP_RETENTION_CONN);
bls_pm_setManualLatency(0);
}
/* Called every main-loop pass. During OTA: answer every connection event
* (no slave latency) and never use deep-sleep retention, which would drop
* the transfer state. Otherwise allow the deepest sleep everywhere. */
/* Window over: non-connectable advertising at the normal interval. In
* this mode the radio does not listen after each packet. */
static void close_window(void)
{
ota_window = 0;
bls_ll_setAdvParam(ADV_INTERVAL, ADV_INTERVAL + ADV_INTERVAL_DELAY,
ADV_TYPE_NONCONNECTABLE_UNDIRECTED, OWN_ADDRESS_PUBLIC,
0, NULL, BLT_ENABLE_ADV_ALL, ADV_FP_NONE);
bls_ll_setAdvEnable(BLC_ADV_ENABLE);
lcd_show_ble(0);
}
_attribute_ram_code_
int ble_task(void)
{
/* An upload that started inside the window may finish after it; any
* other connection is dropped when the window ends. */
if (ota_window && !ota_active &&
clock_time() - window_start > OTA_WINDOW_TICKS) {
if (ble_connected) {
bls_ll_terminateConnection(HCI_ERR_REMOTE_USER_TERM_CONN);
} else {
close_window();
}
}
if (ota_active) {
if (!conn_param_accepted) {
bls_pm_setManualLatency(0);
}
bls_pm_setSuspendMask(SUSPEND_ADV | SUSPEND_CONN);
return 1;
}
bls_pm_setSuspendMask(SUSPEND_ADV | DEEPSLEEP_RETENTION_ADV |
SUSPEND_CONN | DEEPSLEEP_RETENTION_CONN);
return 0;
}
_attribute_ram_code_
void ble_init_deep_retention(void)
{
rf_set_power_level_index(RF_TX_POWER);
blc_ll_recoverDeepRetention();
bls_ota_registerStartCmdCb(ota_start_cb); /* as pvvx */
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "tl_common.h"
/* Connectable advertising + GATT server with the Telink OTA service
* (00010203-0405-0607-0809-0a0b0c0d1912), structured like
* pvvx/ATC_MiThermometer ble.c / app_att.c. */
extern u8 ble_connected; /* a central is connected */
extern u8 ota_active; /* OTA transfer in progress */
extern u8 ota_window; /* connectable OTA window open */
/* Called once from user_init_normal(). open_window: battery was just
* inserted, accept connections for OTA_WINDOW_S; otherwise go straight to
* non-connectable advertising. adv_prepare is called by the stack
* right before every advertising event (pvvx app_advertise_prepare_handler):
* the application piggybacks its periodic work on those wake-ups. */
void ble_init(const u8 *adv, int adv_len, int (*adv_prepare)(void *),
int open_window);
/* Every main-loop pass: sets the sleep mode; returns 1 while OTA runs */
int ble_task(void);
/* Restore what deep-sleep retention loses (from user_init_deepRetn) */
void ble_init_deep_retention(void);
+61
View File
@@ -0,0 +1,61 @@
#include "tl_common.h"
#include "drivers.h"
#include "app_config.h"
#include "i2c.h"
#define I2C_WAIT() while (reg_i2c_status & FLD_I2C_CMD_BUSY)
/* The I2C clock and pin mux live in digital registers that are lost in
* deep-sleep retention, so this is called at the start of every bus session.
* Clock stretching is disabled: the sensor is polled, never stretched. */
_attribute_ram_code_
void i2c_bus_init(void)
{
i2c_gpio_set(I2C_GROUP);
reg_i2c_speed = CLOCK_SYS_CLOCK_HZ / (4 * I2C_SPEED_HZ);
reg_i2c_mode |= FLD_I2C_MASTER_EN;
reg_i2c_mode &= ~FLD_I2C_HOLD_MASTER;
reg_clk_en0 |= FLD_CLK0_I2C_EN;
reg_spi_sp &= ~FLD_SPI_ENABLE; /* pads shared with SPI: force I2C */
}
_attribute_ram_code_
int i2c_write(u8 addr, const u8 *buf, u32 len)
{
int err;
reg_i2c_id = addr << 1;
reg_i2c_ctrl = FLD_I2C_CMD_START | FLD_I2C_CMD_ID;
I2C_WAIT();
err = reg_i2c_status & FLD_I2C_NAK;
while (!err && len--) {
reg_i2c_do = *buf++;
reg_i2c_ctrl = FLD_I2C_CMD_DO;
I2C_WAIT();
err = reg_i2c_status & FLD_I2C_NAK;
}
reg_i2c_ctrl = FLD_I2C_CMD_STOP;
I2C_WAIT();
return err;
}
_attribute_ram_code_
int i2c_read(u8 addr, u8 *buf, u32 len)
{
int err;
reg_i2c_id = (addr << 1) | FLD_I2C_WRITE_READ_BIT;
reg_i2c_ctrl = FLD_I2C_CMD_START | FLD_I2C_CMD_ID;
I2C_WAIT();
err = reg_i2c_status & FLD_I2C_NAK;
while (!err && len--) {
/* NAK (ACK flag) the last byte to end the read */
reg_i2c_ctrl = FLD_I2C_CMD_DI | FLD_I2C_CMD_READ_ID |
(len ? 0 : FLD_I2C_CMD_ACK);
I2C_WAIT();
*buf++ = reg_i2c_di;
}
reg_i2c_ctrl = FLD_I2C_CMD_STOP;
I2C_WAIT();
return err;
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include "tl_common.h"
/* Polled I2C master on the TLSR825x hardware block. Addresses are 7-bit.
* All functions return 0 on success, non-zero on NAK. */
void i2c_bus_init(void);
int i2c_write(u8 addr, const u8 *buf, u32 len);
int i2c_read(u8 addr, u8 *buf, u32 len);
+209
View File
@@ -0,0 +1,209 @@
/*
* LYWSD03MMC segment LCD driver for the 2025 "B1.5" board (pvvx reports it
* as HW B1.1): LCD controller on bit-banged SPI, PD7 = CLK, PB7 = SDI.
* Write-only, no acknowledge.
*
* The frame format and segment map are from pvvx/ATC_MiThermometer
* (lcd_lywsd03mmc.c), used under that project's MIT-style license:
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software ... to deal in the Software without restriction.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*
* Display buffer, byte.bit:
*
* --5.4-- --4.4-- --3.4-- BLE 2.4
* | 5.5 5.0 4.5 4.0 3.5 3.0 o 2.5 (temp unit
* 5.3 --5.1-- --4.1-- --3.1-- 2.5..2.7 symbols)
* | 5.6 5.2 4.6 4.2 3.6 3.2
* --5.7-- --4.7-- * --3.7--
* 4.3
* smiley 2.0..2.2 --1.4-- --0.4--
* 1.5 1.0 0.5 0.0
* --1.1-- --0.1--
* 1.6 1.2 0.6 0.2 % 0.3
* BAT 1.3 --1.7-- --0.7--
*/
#include "tl_common.h"
#include "drivers.h"
#include "drivers/8258/gpio_8258.h"
#include "app_config.h"
#include "lcd.h"
#define SEG_MINUS 0x02 /* middle segment */
#define SYM_POINT 0x08 /* 4.3 */
#define SYM_ONE 0x08 /* 5.3: leading "1" for 100..199 */
#define SYM_PERCENT 0x08 /* 0.3 */
#define SYM_BAT 0x08 /* 1.3 */
#define SYM_DEG_C 0xA0 /* 2.5 + 2.7 */
#define SYM_BLE 0x10 /* 2.4: shown while a BLE central is connected */
#define SYM_H 0x67
#define SYM_i 0x40
#define SYM_L 0xE0
#define SYM_o 0xC6
#define SPI_CLK_DELAY_US 32 /* half clock period: ~15 kHz */
static const u8 digits[10] = {
0xF5, 0x05, 0xD3, 0x97, 0x27, 0xB6, 0xF6, 0x15, 0xF7, 0xB7
};
/* Frame: AA d5..d0 xor(d) 55, sent byte by byte, LSB first */
RAM static u8 buf[6];
RAM static u8 sent[6];
RAM static u8 frame[9];
static void frame_fill(void)
{
u8 chk = 0;
frame[0] = 0xAA;
for (int i = 0; i < 6; i++) {
frame[1 + i] = buf[5 - i];
chk ^= buf[i];
}
frame[7] = chk;
frame[8] = 0x55;
}
/* Data changes while CLK is low and is latched on the rising edge */
_attribute_ram_code_
static void spi_send_byte(u8 b)
{
for (int i = 0; i < 8; i++) {
BM_CLR(reg_gpio_out(GPIO_LCD_CLK), GPIO_LCD_CLK & 0xFF);
if (b & 0x01) {
BM_SET(reg_gpio_out(GPIO_LCD_SDI), GPIO_LCD_SDI & 0xFF);
} else {
BM_CLR(reg_gpio_out(GPIO_LCD_SDI), GPIO_LCD_SDI & 0xFF);
}
sleep_us(SPI_CLK_DELAY_US);
BM_SET(reg_gpio_out(GPIO_LCD_CLK), GPIO_LCD_CLK & 0xFF);
sleep_us(SPI_CLK_DELAY_US);
b >>= 1;
}
sleep_us(SPI_CLK_DELAY_US);
}
/* ~5 ms with interrupts off, only when the content changed. gpio_init()
* makes both pins inputs again after every deep-sleep retention wake-up,
* so they are switched to outputs on each send. */
_attribute_ram_code_
static void lcd_send(void)
{
BM_CLR(reg_gpio_oen(GPIO_LCD_CLK), GPIO_LCD_CLK & 0xFF);
BM_CLR(reg_gpio_oen(GPIO_LCD_SDI), GPIO_LCD_SDI & 0xFF);
u8 r = irq_disable();
for (int i = 0; i < (int)sizeof(frame); i++) {
spi_send_byte(frame[i]);
}
irq_restore(r);
}
static void lcd_flush(void)
{
if (!memcmp(sent, buf, sizeof(buf))) {
return; /* nothing changed: no LCD traffic at all */
}
frame_fill();
lcd_send();
memcpy(sent, buf, sizeof(buf));
}
void lcd_init(void)
{
memset(buf, SEG_MINUS, sizeof(buf)); /* "-- --" */
buf[2] = 0;
pm_wait_ms(30); /* controller start-up, as pvvx (>= 30 ms) */
frame_fill();
lcd_send();
memcpy(sent, buf, sizeof(buf));
}
/* Big digits: temperature in 0.1 degC.
* -9.9 .. 199.9 shown with one decimal, -99 .. -10 as integers. */
static void show_big_x10(s16 n)
{
buf[5] = buf[4] = 0;
if (n > 1999) {
buf[3] = 0; buf[4] = SYM_i; buf[5] = SYM_H; /* "Hi" */
return;
}
if (n <= -995) {
buf[3] = 0; buf[4] = SYM_o; buf[5] = SYM_L; /* "Lo" */
return;
}
if (n < 0) {
n = -n;
buf[5] = SEG_MINUS;
}
if (buf[5] && n > 99) {
n = (n + 5) / 10; /* -10 .. -99: integer */
} else {
buf[4] = SYM_POINT;
}
if (n > 999) {
buf[5] |= SYM_ONE;
}
if (n > 99) {
buf[5] |= digits[n / 100 % 10];
}
buf[4] |= digits[n / 10 % 10]; /* always: "0.x" */
buf[3] = digits[n % 10];
}
/* Small digits: humidity in %. */
static void show_small_pct(int n)
{
buf[1] &= SYM_BAT;
buf[0] = SYM_PERCENT;
if (n > 99) {
buf[0] |= SYM_i; buf[1] |= SYM_H;
return;
}
if (n < 0) {
n = 0;
}
if (n > 9) {
buf[1] |= digits[n / 10];
}
buf[0] |= digits[n % 10];
}
void lcd_show(int valid, s16 t10, int rh, int batt_low)
{
if (valid) {
show_big_x10(t10);
show_small_pct(rh);
buf[2] = (buf[2] & SYM_BLE) | SYM_DEG_C;
}
if (batt_low) {
buf[1] |= SYM_BAT;
} else {
buf[1] &= ~SYM_BAT;
}
lcd_flush();
}
void lcd_show_ble(int on)
{
if (on) {
buf[2] |= SYM_BLE;
} else {
buf[2] &= ~SYM_BLE;
}
lcd_flush();
}
/* Dead-battery screen (as pvvx low_vbat()): battery voltage in the big
* digits ("1.9"), "Lo" in the small ones, battery icon on. */
void lcd_show_low_battery(u16 mv)
{
show_big_x10(mv / 100);
buf[1] = SYM_L | SYM_BAT;
buf[0] = SYM_o;
buf[2] = 0;
lcd_flush();
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "tl_common.h"
/* Segment LCD of the LYWSD03MMC 2025 "B1.5" board (pvvx: HW B1.1):
* controller on bit-banged SPI, PD7 CLK / PB7 SDI, write-only. It holds
* the image by itself, so the CPU only talks to it when the content
* changes. */
void lcd_init(void); /* shows "-- --" */
void lcd_show(int valid, s16 t10, int rh, int batt_low);
void lcd_show_ble(int on); /* BLE symbol while connected */
void lcd_show_low_battery(u16 mv); /* "x.x Lo" + battery icon */
+299
View File
@@ -0,0 +1,299 @@
/*
* BTHome v2 temperature / humidity beacon with LCD and OTA - Xiaomi
* LYWSD03MMC B1.5 (Telink TLSR8251, Telink BLE SDK, no RTOS)
*
* Every 60 s: measure T/RH (SHTV3) and VDD, update the segment LCD and the
* BTHome v2 advertisement sent every 10 s. For OTA_WINDOW_S after the
* battery is inserted the advertising is connectable (1 s interval) so the
* Telink OTA service can be reached; after that it is non-connectable.
*
* Power handling follows pvvx/ATC_MiThermometer:
* - no wake-ups of its own: a measurement is started right after every
* 6th advertising event, while the chip is awake anyway
* - the CPU sleeps (suspend) during the ~12 ms sensor conversion instead
* of stalling; the result is collected by an app wake-up timer
* - VDD is sampled while the sensor converts, i.e. under load
* - deep-sleep retention between events, also while connected
* (slave latency 49 -> one connection event per second)
* - battery protection: < 2.2 V at power-on or < 2.0 V while running ->
* "Lo" on the LCD and 3 min deep sleep, before any flash write
*/
#include "tl_common.h"
#include "drivers.h"
#include "stack/ble/ble.h"
#include "vendor/common/blt_common.h"
#include "app_config.h"
#include "sensor.h"
#include "battery.h"
#include "lcd.h"
#include "ble.h"
#define MEAS_INTERVAL_TICKS ((u32)MEAS_INTERVAL_S * CLOCK_16M_SYS_TIMER_CLK_1S)
#define NAME_LEN (sizeof(DEV_NAME) - 1)
/* Advertising payload (objects in ascending ID order: 0x0C, 0x2E, 0x45) */
enum {
OFS_VOLT = 9, /* voltage, 1 mV, uint16 */
OFS_HUM = 12, /* humidity, 1 %, uint8 */
OFS_TEMP = 14, /* temperature, 0.1 degC, sint16 */
};
#if ADV_NAME_IN_ADV
#define ADV_LEN (18 + NAME_LEN)
#else
#define ADV_LEN 16
#endif
RAM static u8 adv[ADV_LEN] = {
0x02, 0x01, 0x06, /* flags: LE general, no BR/EDR */
0x0C, 0x16, /* service data, 16-bit UUID */
0xD2, 0xFC, /* UUID 0xFCD2 */
0x40, /* BTHome v2, unencrypted, regular interval */
0x0C, 0, 0, /* voltage */
0x2E, 0, /* humidity */
0x45, 0, 0, /* temperature */
#if ADV_NAME_IN_ADV
NAME_LEN + 1, 0x09, /* complete local name, filled in at boot */
#endif
};
typedef char adv_fits_31_bytes[(sizeof(adv) <= 31) ? 1 : -1];
/* Measurement state, kept through deep-sleep retention */
RAM static u8 adv_count; /* advertising events since last measurement */
RAM static u8 start_measure; /* measurement due */
RAM static u8 publish_pending; /* new values to put on air / on the LCD */
RAM static u8 valid; /* at least one good sensor reading */
RAM static u32 time_measure; /* != 0: sensor converting since this tick */
RAM static u32 meas_tick; /* last measurement start (used when connected) */
RAM static s16 t10;
RAM static u8 rh_pct;
RAM static u16 vbat_mv;
static inline void put_le16(u8 *p, u16 v)
{
p[0] = v & 0xFF;
p[1] = v >> 8;
}
/* Deep sleep without retention: the next wake-up is a fresh boot */
static void deep_sleep_s(u32 s)
{
cpu_sleep_wakeup(DEEPSLEEP_MODE, PM_WAKEUP_TIMER,
clock_time() + s * CLOCK_16M_SYS_TIMER_CLK_1S);
while (1) {
}
}
/* pvvx low_vbat(): stop everything, show it, retry in 3 minutes */
static void low_battery(u16 mv)
{
sensor_sleep();
lcd_show_low_battery(mv);
deep_sleep_s(LOW_VBAT_SLEEP_S);
}
_attribute_ram_code_
static void store_result(s32 mc, s32 mrh)
{
s32 pct = (mrh + 500) / 1000;
t10 = (mc + (mc < 0 ? -50 : 50)) / 100;
rh_pct = pct < 0 ? 0 : (pct > 100 ? 100 : pct);
valid = 1;
}
/* App wake-up timer: the sensor conversion is finished (pvvx
* WakeupLowPowerCb). Also called from main_loop() if the stack did not
* sleep at all. A failed read keeps the last good T/RH values. */
_attribute_ram_code_
static void measure_done_cb(int unused)
{
s32 mc, mrh;
(void)unused;
if (!time_measure) {
return;
}
time_measure = 0;
bls_pm_setAppWakeupLowPower(0, 0);
if (!sensor_result(&mc, &mrh)) {
store_result(mc, mrh);
}
publish_pending = 1;
}
/* Start a measurement: trigger the sensor, read VDD while it converts,
* then let the stack sleep until the result is ready. */
_attribute_ram_code_
static void measure_start(void)
{
meas_tick = clock_time();
if (!sensor_start()) {
time_measure = clock_time() | 1;
}
vbat_mv = battery_read_mv();
if (vbat_mv < END_VBAT_MV) {
low_battery(vbat_mv);
}
if (time_measure) {
bls_pm_setAppWakeupLowPower(time_measure + SENSOR_MEAS_TICKS, 1);
} else {
publish_pending = 1; /* sensor absent: voltage only */
}
}
_attribute_ram_code_
static void publish(void)
{
publish_pending = 0;
if (valid) {
put_le16(&adv[OFS_TEMP], (u16)t10);
adv[OFS_HUM] = rh_pct;
}
put_le16(&adv[OFS_VOLT], vbat_mv);
bls_ll_setAdvData(adv, sizeof(adv));
lcd_show(valid, t10, rh_pct, vbat_mv < LOW_BATT_MV);
}
/* Called by the stack right before every advertising event (pvvx
* app_advertise_prepare_handler): count events, flag a measurement on
* every MEAS_EVERY_N_ADV-th one. The measurement itself starts in
* main_loop() once the event is done. While the OTA window runs at the
* fast interval, main_loop() uses a timer instead. */
_attribute_ram_code_
static int adv_prepare_cb(void *p)
{
(void)p;
if (!ota_window && ++adv_count >= MEAS_EVERY_N_ADV) {
adv_count = 0;
start_measure = 1;
}
return 1; /* send this advertising packet */
}
static void user_init_normal(void)
{
blc_ll_initBasicMCU();
adc_power_on_sar_adc(0);
lpc_power_down();
/* Analog pull resistors are kept through deep sleep: set once */
gpio_setup_up_down_resistor(I2C_SCL, PULL_I2C);
gpio_setup_up_down_resistor(I2C_SDA, PULL_I2C);
gpio_setup_up_down_resistor(GPIO_LCD_PB6, PULL_LCD_PB6);
gpio_setup_up_down_resistor(GPIO_LCD_CLK, PULL_LCD_SER);
gpio_setup_up_down_resistor(GPIO_LCD_SDI, PULL_LCD_SER);
gpio_setup_up_down_resistor(GPIO_KEY, PULL_KEY);
/* Battery first: nothing below may write flash on a dying cell
* (pvvx start_tst_battery) */
vbat_mv = battery_read_mv();
if (vbat_mv < BOOT_VBAT_MV) {
sensor_init(); /* idle -> sleep */
lcd_init();
low_battery(vbat_mv);
}
/* The library writes flash for OTA: clear write protection left by
* other firmware (as pvvx, once per power-on) */
flash_unlock();
random_generator_init();
#if ADV_NAME_IN_ADV
memcpy(&adv[18], DEV_NAME, NAME_LEN);
#endif
lcd_init(); /* shows "-- --" until the first good reading */
sensor_init(); /* put the SHTV3 to sleep */
/* First measurement, blocking, so the first packet carries data */
if (!sensor_start()) {
s32 mc, mrh;
vbat_mv = battery_read_mv();
pm_wait_us(SENSOR_MEAS_US);
if (!sensor_result(&mc, &mrh)) {
store_result(mc, mrh);
}
}
publish();
/* DEEP_ANA_REG0 survives deep sleep and software reboots and is
* cleared only by a power cycle (pvvx uses it the same way) */
int power_on = analog_read(DEEP_ANA_REG0) != 0x55;
analog_write(DEEP_ANA_REG0, 0x55);
bls_pm_registerAppWakeupLowPowerCb(measure_done_cb);
ble_init(adv, sizeof(adv), adv_prepare_cb, power_on);
meas_tick = clock_time();
}
_attribute_ram_code_
static void user_init_deepRetn(void)
{
blc_ll_initBasicMCU();
ble_init_deep_retention();
}
_attribute_ram_code_
static void main_loop(void)
{
blt_sdk_main_loop();
if (ble_task()) {
return; /* OTA in progress: no measurements */
}
if (time_measure &&
clock_time() - time_measure > SENSOR_MEAS_TICKS) {
measure_done_cb(0); /* stack did not sleep in between */
}
/* While connected there are no advertising events to count, and in
* the OTA window they come every second */
if ((ble_connected || ota_window) &&
clock_time() - meas_tick >= MEAS_INTERVAL_TICKS) {
start_measure = 1;
}
if (start_measure && !time_measure) {
start_measure = 0;
measure_start();
}
if (publish_pending) {
publish();
}
}
_attribute_ram_code_
void irq_handler(void)
{
irq_blt_sdk_handler();
}
_attribute_ram_code_
int main(void)
{
blc_pm_select_internal_32k_crystal(); /* no 32 kHz crystal fitted */
cpu_wakeup_init();
int deep_ret = pm_is_MCU_deepRetentionWakeup();
gpio_init(!deep_ret);
clock_init(SYS_CLK_TYPE);
rf_drv_init(RF_MODE_BLE_1M);
reg_clk_en0 = FLD_CLK0_SWIRE_EN; /* gate unused peripheral clocks */
blc_app_loadCustomizedParameters(); /* factory crystal trim */
if (deep_ret) {
user_init_deepRetn();
} else {
user_init_normal();
}
irq_enable();
for (;;) {
main_loop();
}
}
+94
View File
@@ -0,0 +1,94 @@
#include "tl_common.h"
#include "drivers.h"
#include "app_config.h"
#include "i2c.h"
#include "sensor.h"
/* SHTV3 / SHTC3 on the B1.5 board */
#define SHTC3_ADDR 0x70
#define SHTC3_WAKEUP_US 240
static const u8 shtc3_wakeup[] = { 0x35, 0x17 };
static const u8 shtc3_sleep[] = { 0xB0, 0x98 };
static const u8 shtc3_meas[] = { 0x78, 0x66 }; /* normal mode, T first, no stretching */
_attribute_ram_code_
static u8 crc8(const u8 *p, int len)
{
u8 crc = 0xFF;
while (len--) {
crc ^= *p++;
for (int i = 0; i < 8; i++) {
crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1);
}
}
return crc;
}
/* Wake the sensor once and put it straight back to sleep (~0.3 uA).
* It powers up in idle mode, which draws far more than sleep. */
int sensor_init(void)
{
int err;
i2c_bus_init();
err = i2c_write(SHTC3_ADDR, shtc3_wakeup, sizeof(shtc3_wakeup));
if (!err) {
sleep_us(SHTC3_WAKEUP_US);
i2c_write(SHTC3_ADDR, shtc3_sleep, sizeof(shtc3_sleep));
}
return err;
}
_attribute_ram_code_
int sensor_start(void)
{
int err;
i2c_bus_init();
err = i2c_write(SHTC3_ADDR, shtc3_wakeup, sizeof(shtc3_wakeup));
if (err) {
return err;
}
sleep_us(SHTC3_WAKEUP_US);
err = i2c_write(SHTC3_ADDR, shtc3_meas, sizeof(shtc3_meas));
if (err) {
i2c_write(SHTC3_ADDR, shtc3_sleep, sizeof(shtc3_sleep));
}
return err;
}
/* Raw 16-bit words -> milli-units. The x/65536 scaling is done as
* (x * k) >> 13, which fits in 32 bits:
* 175000 / 8 = 21875, 100000 / 8 = 12500 */
_attribute_ram_code_
int sensor_result(s32 *t_mc, s32 *rh_mpct)
{
u8 buf[6];
int err;
i2c_bus_init(); /* I2C registers do not survive deep-sleep retention */
err = i2c_read(SHTC3_ADDR, buf, sizeof(buf));
i2c_write(SHTC3_ADDR, shtc3_sleep, sizeof(shtc3_sleep));
if (err || crc8(&buf[0], 2) != buf[2] || crc8(&buf[3], 2) != buf[5]) {
return -1;
}
u32 t_raw = (buf[0] << 8) | buf[1];
u32 h_raw = (buf[3] << 8) | buf[4];
*t_mc = (s32)((t_raw * 21875) >> 13) - 45000;
*rh_mpct = (s32)((h_raw * 12500) >> 13);
return 0;
}
/* The sleep command is NAKed while a conversion is running, so allow
* for one to finish first. */
void sensor_sleep(void)
{
i2c_bus_init();
sleep_us(SENSOR_MEAS_US);
i2c_write(SHTC3_ADDR, shtc3_sleep, sizeof(shtc3_sleep));
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "tl_common.h"
/* Temperature/humidity sensor of the LYWSD03MMC B1.5:
* SHTV3 (SHTC3-compatible) at I2C address 0x70.
*
* A measurement is split in two so the CPU can sleep while the sensor
* converts (as pvvx SENSOR_SLEEP_MEASURE does):
* sensor_start() wake the sensor and trigger a conversion
* ... SENSOR_MEAS_TICKS later ...
* sensor_result() read the result and put the sensor back to sleep */
#define SENSOR_MEAS_US 12500 /* SHTC3 normal mode: max 12.1 ms */
#define SENSOR_MEAS_TICKS (SENSOR_MEAS_US * CLOCK_16M_SYS_TIMER_CLK_1US)
int sensor_init(void); /* 0 = sensor answered */
int sensor_start(void); /* 0 = conversion started */
int sensor_result(s32 *t_mc, s32 *rh_mpct); /* milli-degC, milli-%; 0 = ok */
void sensor_sleep(void); /* force sleep (e.g. low battery) */