续上一篇帖子【eDIY】蓝牙低功耗温湿度计过程贴-在vscode上搭建环境,连接蓝牙-电子产品世界论坛 (eepw.com.cn)的例程,我们在例程中加入我们的sht30驱动代码,使用的型号为本次活动提供的sht30,查阅数据手册,发现该传感器区别于普通的sht30,这个是adc输出的接口,那么我们就只需要配置板子的adc输入通道,换算成温湿度即可。

```
这是开发过程中的一些操作步骤,下面是代码部分
/* main.c - Application main entry point */
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/services/bas.h>
#include <zephyr/bluetooth/services/cts.h>
#include <zephyr/bluetooth/services/hrs.h>
#include <zephyr/bluetooth/services/ias.h>
#include <zephyr/drivers/adc.h>
/* Custom Service Variables */
#define BT_UUID_CUSTOM_SERVICE_VAL \
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)
static const struct bt_uuid_128 vnd_uuid = BT_UUID_INIT_128(
BT_UUID_CUSTOM_SERVICE_VAL);
static const struct bt_uuid_128 vnd_enc_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1));
static const struct bt_uuid_128 vnd_auth_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef2));
#define VND_MAX_LEN 20
#define BT_HR_HEARTRATE_DEFAULT_MIN 90U
#define BT_HR_HEARTRATE_DEFAULT_MAX 160U
static uint8_t vnd_value[VND_MAX_LEN + 1] = { 'V', 'e', 'n', 'd', 'o', 'r'};
static uint8_t vnd_auth_value[VND_MAX_LEN + 1] = { 'V', 'e', 'n', 'd', 'o', 'r'};
static uint8_t vnd_wwr_value[VND_MAX_LEN + 1] = { 'V', 'e', 'n', 'd', 'o', 'r' };
static ssize_t read_vnd(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const char *value = attr->user_data;
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
strlen(value));
}
static ssize_t write_vnd(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
uint8_t *value = attr->user_data;
if (offset + len > VND_MAX_LEN) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
memcpy(value + offset, buf, len);
value[offset + len] = 0;
return len;
}
static uint8_t simulate_vnd;
static uint8_t indicating;
static struct bt_gatt_indicate_params ind_params;
static char vnd_ind_data[30]; /* Buffer for SHT30 temp/humidity string */
/* SHT30 Analog Sensor ADC Configuration
* PTD2 = ADC0_A6 (channel 6) -> Temperature
* PTD0 = ADC0_A5 (channel 5) -> Humidity
* SHT30 output: 0.3~2.7V linear analog voltage
* Formulas (from datasheet):
* Temperature(??C) = -66.875 + 72.917 * V
* Humidity(%RH) = -12.5 + 41.667 * V
*/
/* LPADC: channel_id selects CMD register slot, input_positive selects hardware channel */
/* ADC0_A6 = channel 6 side A -> input_positive = 0x06 */
/* ADC0_A5 = channel 5 side A -> input_positive = 0x05 */
#define ADC_TEMP_CMD_ID 0 /* CMD slot 0 for temperature */
#define ADC_HUM_CMD_ID 1 /* CMD slot 1 for humidity */
#define ADC_TEMP_HW_CHAN 6 /* ADC0_A6 = PTD2 */
#define ADC_HUM_HW_CHAN 5 /* ADC0_A5 = PTD0 */
/*
* LPADC on MCXW716 only supports ADC_GAIN_1 (full scale).
* Gain 1/2 (kLPADC_SamplePartScale = 30/64) is NOT supported
* because FSL_FEATURE_LPADC_HAS_CMDL_CSCALE is not defined.
* See: adc_mcux_lpadc.c:270-277
*
* With gain=1: Vin = raw * Vref / 4096
* Vref = 1.8V (internal VREF regulator)
* Max measurable voltage: 1.8V
* SHT30 at room temp: T~1.26V, RH~1.5V (within range)
*/
#define ADC_RESOLUTION 12
#define VREF_MV 1800 /* Internal VREF = 1.8V */
#define ABS(x) ((x) < 0 ? -(x) : (x))
#define ADC_NODE DT_ALIAS(adc0)
static const struct device *adc_dev;
static int16_t adc_sample_buffer[1];
static int adc_init(void)
{
int err;
adc_dev = DEVICE_DT_GET(ADC_NODE);
if (!device_is_ready(adc_dev)) {
printk("ADC device not ready\n");
return -ENODEV;
}
/* Setup temperature channel (ADC0_A6, PTD2) */
/* input_positive = ADC_TEMP_HW_CHAN (channel 6, side A)
*
* LPADC reference selection:
* - ADC_REF_EXTERNAL1 = use SoC internal VREF regulator (1.8V)
* - ADC_REF_INTERNAL is NOT supported by nxp,lpc-lpadc driver
* (driver rejects with -EINVAL at mcux_lpadc.c:285-299)
*
* LPADC gain: MCXW716 only supports ADC_GAIN_1 (full scale).
* Gain 1/2 (30/64 scaling) is NOT available.
* See: adc_mcux_lpadc.c:270-277
* With gain=1: Vin = raw * 1800 / 4096, range = 0~1.8V
*/
struct adc_channel_cfg temp_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_EXTERNAL1,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = ADC_TEMP_CMD_ID,
.input_positive = ADC_TEMP_HW_CHAN,
.differential = 0,
};
/* Setup humidity channel (ADC0_A5, PTD0) */
/* input_positive = ADC_HUM_HW_CHAN (channel 5, side A) */
struct adc_channel_cfg hum_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_EXTERNAL1,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = ADC_HUM_CMD_ID,
.input_positive = ADC_HUM_HW_CHAN,
.differential = 0,
};
err = adc_channel_setup(adc_dev, &temp_cfg);
if (err != 0) {
printk("ADC temp channel setup failed (err %d)\n", err);
return err;
}
err = adc_channel_setup(adc_dev, &hum_cfg);
if (err != 0) {
printk("ADC humidity channel setup failed (err %d)\n", err);
return err;
}
printk("ADC initialized for SHT30 sensor\n");
return 0;
}
static int adc_read_channel(uint8_t channel, int32_t *mv_out, int16_t *raw_out)
{
int err;
struct adc_sequence sequence = {
.channels = BIT(channel),
.buffer = adc_sample_buffer,
.buffer_size = sizeof(adc_sample_buffer),
.resolution = ADC_RESOLUTION,
.oversampling = 0,
};
err = adc_read(adc_dev, &sequence);
if (err != 0) {
printk("ADC read failed (err %d)\n", err);
return err;
}
if (raw_out != NULL) {
*raw_out = adc_sample_buffer[0];
}
*mv_out = (int32_t)adc_sample_buffer[0];
/* LPADC gain=1 (full scale): Vin = raw * Vref / 4096
* adc_raw_to_millivolts: mv = raw * 1800 * 1 / 4096
* Range: 0 ~ 1.8V (SHT30 analog output 0.3~2.7V, only ~1.5V at mid-range fits)
* SHT30 at 25°C/65%RH: T~1.26V, H~1.5V (within 0~1.8V range).
* If sensor output >1.8V, an external voltage divider is needed.
*/
adc_raw_to_millivolts(VREF_MV, ADC_GAIN_1,
ADC_RESOLUTION, mv_out);
return 0;
}
static void vnd_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
simulate_vnd = (value == BT_GATT_CCC_INDICATE) ? 1 : 0;
}
static void indicate_cb(struct bt_conn *conn,
struct bt_gatt_indicate_params *params, uint8_t err)
{
printk("Indication %s\n", err != 0U ? "fail" : "success");
}
static void indicate_destroy(struct bt_gatt_indicate_params *params)
{
printk("Indication complete\n");
indicating = 0U;
}
#define VND_LONG_MAX_LEN 74
static uint8_t vnd_long_value[VND_LONG_MAX_LEN + 1] = {
'V', 'e', 'n', 'd', 'o', 'r', ' ', 'd', 'a', 't', 'a', '1',
'V', 'e', 'n', 'd', 'o', 'r', ' ', 'd', 'a', 't', 'a', '2',
'V', 'e', 'n', 'd', 'o', 'r', ' ', 'd', 'a', 't', 'a', '3',
'V', 'e', 'n', 'd', 'o', 'r', ' ', 'd', 'a', 't', 'a', '4',
'V', 'e', 'n', 'd', 'o', 'r', ' ', 'd', 'a', 't', 'a', '5',
'V', 'e', 'n', 'd', 'o', 'r', ' ', 'd', 'a', 't', 'a', '6',
'.', ' ' };
static ssize_t write_long_vnd(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const void *buf,
uint16_t len, uint16_t offset, uint8_t flags)
{
uint8_t *value = attr->user_data;
if (flags & BT_GATT_WRITE_FLAG_PREPARE) {
return 0;
}
if (offset + len > VND_LONG_MAX_LEN) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
memcpy(value + offset, buf, len);
value[offset + len] = 0;
return len;
}
static const struct bt_uuid_128 vnd_long_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef3));
static struct bt_gatt_cep vnd_long_cep = {
.properties = BT_GATT_CEP_RELIABLE_WRITE,
};
static const struct bt_uuid_128 vnd_write_cmd_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef4));
static ssize_t write_without_rsp_vnd(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
uint8_t *value = attr->user_data;
if (!(flags & BT_GATT_WRITE_FLAG_CMD)) {
/* Write Request received. Reject it since this Characteristic
* only accepts Write Without Response.
*/
return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED);
}
if (offset + len > VND_MAX_LEN) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
memcpy(value + offset, buf, len);
value[offset + len] = 0;
return len;
}
/* Vendor Primary Service Declaration */
BT_GATT_SERVICE_DEFINE(vnd_svc,
BT_GATT_PRIMARY_SERVICE(&vnd_uuid),
BT_GATT_CHARACTERISTIC(&vnd_enc_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE |
BT_GATT_CHRC_INDICATE,
BT_GATT_PERM_READ_ENCRYPT |
BT_GATT_PERM_WRITE_ENCRYPT,
read_vnd, write_vnd, vnd_value),
BT_GATT_CCC(vnd_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT),
BT_GATT_CHARACTERISTIC(&vnd_auth_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_AUTHEN |
BT_GATT_PERM_WRITE_AUTHEN,
read_vnd, write_vnd, vnd_auth_value),
BT_GATT_CHARACTERISTIC(&vnd_long_uuid.uuid, BT_GATT_CHRC_READ |
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_EXT_PROP,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE |
BT_GATT_PERM_PREPARE_WRITE,
read_vnd, write_long_vnd, &vnd_long_value),
BT_GATT_CEP(&vnd_long_cep),
BT_GATT_CHARACTERISTIC(&vnd_write_cmd_uuid.uuid,
BT_GATT_CHRC_WRITE_WITHOUT_RESP,
BT_GATT_PERM_WRITE, NULL,
write_without_rsp_vnd, &vnd_wwr_value),
);
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL,
BT_UUID_16_ENCODE(BT_UUID_HRS_VAL),
BT_UUID_16_ENCODE(BT_UUID_BAS_VAL),
BT_UUID_16_ENCODE(BT_UUID_CTS_VAL)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_CUSTOM_SERVICE_VAL),
};
static const struct bt_data sd[] = {
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};
void mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
printk("Updated MTU: TX: %d RX: %d bytes\n", tx, rx);
}
static struct bt_gatt_cb gatt_callbacks = {
.att_mtu_updated = mtu_updated
};
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed, err 0x%02x %s\n", err, bt_hci_err_to_str(err));
} else {
printk("Connected\n");
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected, reason 0x%02x %s\n", reason, bt_hci_err_to_str(reason));
}
static void alert_stop(void)
{
printk("Alert stopped\n");
}
static void alert_start(void)
{
printk("Mild alert started\n");
}
static void alert_high_start(void)
{
printk("High alert started\n");
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
BT_IAS_CB_DEFINE(ias_callbacks) = {
.no_alert = alert_stop,
.mild_alert = alert_start,
.high_alert = alert_high_start,
};
static void bt_ready(void)
{
int err;
printk("Bluetooth initialized\n");
if (IS_ENABLED(CONFIG_SETTINGS)) {
settings_load();
}
err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err) {
printk("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
}
static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Passkey for %s: %06u\n", addr, passkey);
}
static void auth_cancel(struct bt_conn *conn)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Pairing cancelled: %s\n", addr);
}
static struct bt_conn_auth_cb auth_cb_display = {
.passkey_display = auth_passkey_display,
.passkey_entry = NULL,
.cancel = auth_cancel,
};
static void bas_notify(void)
{
uint8_t battery_level = bt_bas_get_battery_level();
battery_level--;
if (!battery_level) {
battery_level = 100U;
}
bt_bas_set_battery_level(battery_level);
}
static uint8_t bt_heartrate = BT_HR_HEARTRATE_DEFAULT_MIN;
static void hrs_notify(void)
{
/* Heartrate measurements simulation */
bt_heartrate++;
if (bt_heartrate == BT_HR_HEARTRATE_DEFAULT_MAX) {
bt_heartrate = BT_HR_HEARTRATE_DEFAULT_MIN;
}
bt_hrs_notify(bt_heartrate);
}
/**
* variable to hold reference milliseconds to epoch when device booted
* this is only for demo purpose, for more precise synchronization please
* review clock_settime API implementation.
*/
static struct bt_cts_local_time local_time = {
.timezone_offset = BT_CTS_TIMEZONE_DEFAULT_VALUE,
.dst_offset = BT_CTS_DST_OFFSET_UNKNOWN,
};
static bool cts_notification_enabled;
static int64_t unix_ms_ref;
static void cts_notification_changed_cb(bool enabled)
{
cts_notification_enabled = enabled;
}
static int cts_time_write_cb(struct bt_cts_time_format *cts_time)
{
int err;
int64_t unix_ms;
if (IS_ENABLED(CONFIG_BT_CTS_HELPER_API)) {
err = bt_cts_time_to_unix_ms(cts_time, &unix_ms);
if (err != 0) {
return err;
}
} else {
return -ENOTSUP;
}
/* recalculate reference value */
unix_ms_ref = unix_ms - k_uptime_get();
return 0;
}
static int cts_fill_current_cts_time_cb(struct bt_cts_time_format *cts_time)
{
int64_t unix_ms = unix_ms_ref + k_uptime_get();
if (IS_ENABLED(CONFIG_BT_CTS_HELPER_API)) {
return bt_cts_time_from_unix_ms(cts_time, unix_ms);
} else {
return -ENOTSUP;
}
}
static int cts_local_time_write_cb(const struct bt_cts_local_time *cts_local_time)
{
memcpy(&local_time, cts_local_time, sizeof(local_time));
return 0;
}
static int cts_fill_local_time_cb(struct bt_cts_local_time *cts_local_time)
{
memcpy(cts_local_time, &local_time, sizeof(local_time));
return 0;
}
const struct bt_cts_cb cts_cb = {
.notification_changed = cts_notification_changed_cb,
.cts_time_write = cts_time_write_cb,
.fill_current_cts_time = cts_fill_current_cts_time_cb,
.cts_local_time_write = cts_local_time_write_cb,
.fill_current_cts_local_time = cts_fill_local_time_cb,
};
static int bt_hrs_ctrl_point_write(uint8_t request)
{
printk("HRS Control point request: %d\n", request);
if (request != BT_HRS_CONTROL_POINT_RESET_ENERGY_EXPANDED_REQ) {
return -ENOTSUP;
}
bt_heartrate = BT_HR_HEARTRATE_DEFAULT_MIN;
return 0;
}
static struct bt_hrs_cb hrs_cb = {
.ctrl_point_write = bt_hrs_ctrl_point_write,
};
int main(void)
{
struct bt_gatt_attr *vnd_ind_attr;
char str[BT_UUID_STR_LEN];
int err;
err = bt_enable(NULL);
if (err != 0) {
printk("Bluetooth init failed (err %d)\n", err);
return 0;
}
bt_ready();
bt_cts_init(&cts_cb);
bt_hrs_cb_register(&hrs_cb);
bt_gatt_cb_register(&gatt_callbacks);
bt_conn_auth_cb_register(&auth_cb_display);
/* Initialize ADC for SHT30 sensor */
err = adc_init();
if (err != 0) {
printk("ADC init failed (err %d)\n", err);
}
vnd_ind_attr = bt_gatt_find_by_uuid(vnd_svc.attrs, vnd_svc.attr_count,
&vnd_enc_uuid.uuid);
bt_uuid_to_str(&vnd_enc_uuid.uuid, str, sizeof(str));
printk("Indicate VND attr %p (UUID %s)\n", vnd_ind_attr, str);
/* Implement notification. At the moment there is no suitable way
* of starting delayed work so we do it here
*/
while (1) {
k_sleep(K_SECONDS(2));
/* Read SHT30 temperature and humidity via ADC */
int32_t temp_mv = 0, hum_mv = 0;
int16_t temp_raw = 0, hum_raw = 0;
bool adc_ok = true;
if (adc_read_channel(ADC_TEMP_CMD_ID, &temp_mv, &temp_raw) != 0) {
adc_ok = false;
}
if (adc_read_channel(ADC_HUM_CMD_ID, &hum_mv, &hum_raw) != 0) {
adc_ok = false;
}
if (adc_ok) {
/* SHT30 conversion formulas (integer math, result x10) */
/* T(??C)*1000 = -66875 + 72917 * mv / 1000 */
/* RH(%)*1000 = -12500 + 41667 * mv / 1000 */
int32_t temp_x1000 = -66875 + (72917 * temp_mv) / 1000;
int32_t hum_x1000 = -12500 + (41667 * hum_mv) / 1000;
int temp_x10 = temp_x1000 / 100;
int hum_x10 = hum_x1000 / 100;
/* Clamp to valid ranges */
if (temp_x10 < -400) temp_x10 = -400;
if (temp_x10 > 1250) temp_x10 = 1250;
if (hum_x10 < 0) hum_x10 = 0;
if (hum_x10 > 1000) hum_x10 = 1000;
/* Format: "T:25.3C,H:65.2%" */
snprintf(vnd_ind_data, sizeof(vnd_ind_data),
"T:%d.%dC,H:%d.%d%%\n",
temp_x10 / 10, ABS(temp_x10 % 10),
hum_x10 / 10, ABS(hum_x10 % 10));
/* Print to serial port */
printk("SHT30: T=%d.%d??C, H=%d.%d%%RH \n",
temp_x10 / 10, ABS(temp_x10 % 10),
hum_x10 / 10, ABS(hum_x10 % 10));
}
/* Current time update notification example
* For testing purposes, we send a manual update notification every second.
* In production `bt_cts_send_notification` should only be used when time is changed
*/
if (cts_notification_enabled) {
bt_cts_send_notification(BT_CTS_UPDATE_REASON_MANUAL);
}
/* Heartrate measurements simulation */
hrs_notify();
/* Battery level simulation */
bas_notify();
/* Vendor indication: send SHT30 temp/humidity data */
if (simulate_vnd && vnd_ind_attr) {
if (indicating) {
continue;
}
ind_params.attr = vnd_ind_attr;
ind_params.func = indicate_cb;
ind_params.destroy = indicate_destroy;
ind_params.data = vnd_ind_data;
ind_params.len = strlen(vnd_ind_data);
if (bt_gatt_indicate(NULL, &ind_params) == 0) {
indicating = 1U;
printk("BT Indicate sent: %s\n", vnd_ind_data);
}
}
}
return 0;
}运行效果如下:
我要赚赏金
