- 2024-12-11
-
加入了学习《followme3——基于lvgl多功能摆件的设计》,观看 followme3完整介绍内容
- 2024-12-08
-
回复了主题帖:
【Follow me第二季第4期】任务四 RGB LED亮度显示PDM麦克风收到的声音大小
想着视频总结的时候发一下好了.
-
回复了主题帖:
【Follow me第二季第4期】任务一 搭建环境&Blink三色LED / 串口打印
我视频上不是演示了么.
- 2024-12-06
-
加入了学习《【Follow me第二季第4期】任务汇报》,观看 【Follow me第二季第4期】任务汇报
-
发表了主题帖:
【Follow me第二季第4期】任务四 RGB LED亮度显示PDM麦克风收到的声音大小
本帖最后由 Netseye 于 2024-12-6 16:11 编辑
要在 Arduino Nano RP2040 Connect 上通过 RGB LED 显示 PDM 麦克风 收到的声音大小,可以通过读取麦克风的音量数据,并根据音量调整 RGB LED 的颜色和亮度。
主要步骤:
读取 PDM 麦克风音频数据。
计算音频信号的强度(音量)。
调整 RGB LED 的颜色和亮度,根据音量大小显示不同的效果。
实现
计算声音的大小(音量或振幅)通常需要对采样数据进行处理。在数字音频处理中,声音的大小可以通过计算振幅的平均值或最大值来近似表示。以下是几种常见的计算方法:
1. 峰值振幅法(Peak Amplitude)
计算采样数据中的最大或最小振幅,取绝对值的最大值。
int16_t getMaxAmplitude(short *buffer, int length) {
int16_t maxAmplitude = 0;
for (int i = 0; i < length; i++) {
int16_t amplitude = abs(buffer[i]);
if (amplitude > maxAmplitude) {
maxAmplitude = amplitude;
}
}
return maxAmplitude; // 返回最大振幅
}
优点:简单、直观。
缺点:容易受短时间内的尖锐噪声影响。
2. 均方根法(RMS,Root Mean Square)
RMS 是衡量声音能量的常用方法,它计算采样值的平方平均再开方,能更稳定地反映声音的大小。
float getRMSAmplitude(short *buffer, int length) {
long sum = 0;
for (int i = 0; i < length; i++) {
sum += (long)buffer[i] * buffer[i]; // 计算平方和
}
float rms = sqrt(sum / (float)length); // 计算平方平均值并开方
return rms;
}
优点:更稳定,能更准确地反映整体音量。
缺点:计算稍微复杂一些。
3. 平均绝对振幅法(Mean Absolute Amplitude)
计算所有采样值的绝对值平均。
float getAverageAmplitude(short *buffer, int length) {
long sum = 0;
for (int i = 0; i < length; i++) {
sum += abs(buffer[i]); // 计算绝对值和
}
return sum / (float)length; // 返回平均值
}
优点:简单且能消除正负波形的抵消影响。
缺点:没有 RMS 稳定,但比峰值法好。
能够更灵敏一些我们采用峰值振幅法(Peak Amplitude) 来计算音量
您可以根据 音量 值的大小划分不同的等级。例如,设置 4 个等级:
低音量:音量 值在 0 到 300 范围内
中低音量:音量 值在 300 到 500 范围内
中高音量:音量 值在 500 到 3000 范围内
高音量:音量 值大于 3000
将 音量 值映射到 LED 亮度或颜色:
根据音量等级,设置 LED 的颜色或亮度。例如:
低音量:红色 LED 亮起
中低音量:绿色 LED 亮起
中高音量:蓝色 LED 亮起
高音量:RGB 全亮
代码示例:
以下代码实现了通过 RGB LED 显示声音的强度,根据声音的大小变化调整 LED 的亮度和颜色。
#include <PDM.h>
#include <WiFiNINA.h>
// 默认输出通道数
static const char channels = 1;
// 默认 PCM 输出频率
static const int frequency = 20000;
// 用于读取样本的缓冲区,每个样本是 16 位
short sampleBuffer[512];
// 已读取的音频样本数量
volatile int samplesRead;
void setup() {
Serial.begin(9600); // 初始化串口通信
while (!Serial); // 等待串口连接
pinMode(LEDR, OUTPUT); // 设置红色 LED 引脚为输出
pinMode(LEDG, OUTPUT); // 设置绿色 LED 引脚为输出
pinMode(LEDB, OUTPUT); // 设置蓝色 LED 引脚为输出
// 配置数据接收回调函数
PDM.onReceive(onPDMdata);
// 初始化 PDM 麦克风:
// - 一个通道(单声道模式)
// - 20 kHz 采样率
if (!PDM.begin(channels, frequency)) {
Serial.println("无法启动 PDM!");
while (1); // 启动失败时停止程序
}
}
void loop() {
// 等待读取样本
if (samplesRead) {
float rms = getMaxAmplitude(sampleBuffer, samplesRead);
controlLEDs(rms);
Serial.println(rms);
// 清空已读取的样本数量
samplesRead = 0;
}
}
void onPDMdata() {
// 查询可用字节数
int bytesAvailable = PDM.available();
// 从 PDM 麦克风读取数据到样本缓冲区
PDM.read(sampleBuffer, bytesAvailable);
// 16 位样本,每个样本占 2 字节
samplesRead = bytesAvailable / 2;
}
int16_t getMaxAmplitude(short *buffer, int length) {
int16_t maxAmplitude = 0;
for (int i = 0; i < length; i++) {
int16_t amplitude = abs(buffer[i]);
if (amplitude > maxAmplitude) {
maxAmplitude = amplitude;
}
}
return maxAmplitude; // 返回最大振幅
}
void controlLEDs(float rms) {
// 按 RMS 值进行分级
if (rms < 300) {
digitalWrite(LEDR, HIGH);
digitalWrite(LEDG, LOW);
digitalWrite(LEDB, LOW);
}
else if (rms < 500) {
digitalWrite(LEDR, LOW);
digitalWrite(LEDG, HIGH);
digitalWrite(LEDB, LOW);
}
else if (rms < 3000) {
digitalWrite(LEDR, LOW);
digitalWrite(LEDG, LOW);
digitalWrite(LEDB, HIGH);
}
else {
digitalWrite(LEDR, HIGH);
digitalWrite(LEDG, HIGH);
digitalWrite(LEDB, HIGH);
}
}
代码说明:
初始化 PDM 麦克风:
PDM.begin(1, 16000) 启动单声道,采样率为 16kHz。
设置回调函数 PDM.onReceive(onPDMdata) 处理采样数据。
计算最大振幅:
getMaxAmplitude() 函数计算采样数据中的最大振幅,反映音频信号的强度。
RGB LED 控制:
setLEDColor() 函数将音量值映射到 LED 的亮度。map() 函数将音量值(0 到 255)映射到 RGB LED 的亮度值。
使用红色和绿色 LED 显示不同的颜色,蓝色 LED 不随音量变化。
音量和颜色映射:
随着音量的增大,红色和绿色 LED 的亮度增亮,从而实现声音大小与 RGB LED 亮度和颜色的动态显示。
调试:
上传代码:将代码上传到 Arduino Nano RP2040 Connect 板子。
串口监视器:打开串口监视器查看音量数据。
LED 反馈:观察 RGB LED 的颜色和亮度变化,根据麦克风拾取到的音频信号显示不同的颜色和亮度。
-
发表了主题帖:
【Follow me第二季第4期】任务三 学调试PDM麦克风,通过串口打印收音数据和音频波形。
Arduino Nano RP2040 Connect 板载了 MP34DT06 PDM 数字麦克风,它是一种基于脉冲密度调制(PDM)的麦克风。通过采样 PDM 信号可以获取音频数据,适用于语音识别、音频采集等场景。
PDM 麦克风基础知识
PDM(Pulse Density Modulation)是一种以高频率表示声音强度的调制方式。
相比于传统的模拟麦克风,PDM 麦克风输出的是数字信号,适合直接处理数字音频。
2. 示例代码
下面的代码演示了如何初始化 PDM 麦克风并通过串口打印音频采样数据:
C++
#include <PDM.h>
// 默认输出通道数
static const char channels = 1;
// 默认 PCM 输出频率
static const int frequency = 16000;
// 用于读取样本的缓冲区,每个样本是 16 位
short sampleBuffer[512];
// 已读取的音频样本数量
volatile int samplesRead;
void setup() {
Serial.begin(9600); // 初始化串口通信
while (!Serial); // 等待串口连接
// 配置数据接收回调函数
PDM.onReceive(onPDMdata);
// 初始化 PDM 麦克风:
// - 一个通道(单声道模式)
// - 20 kHz 采样率
if (!PDM.begin(channels, frequency)) {
Serial.println("无法启动 PDM!");
while (1); // 启动失败时停止程序
}
}
void loop() {
// 等待读取样本
if (samplesRead) {
// 将样本数据打印到串口监视器或绘图工具中
for (int i = 0; i < samplesRead; i++) {
Serial.println(sampleBuffer); // 打印当前样本数据
}
// 清空已读取的样本数量
samplesRead = 0;
}
}
void onPDMdata() {
// 查询可用字节数
int bytesAvailable = PDM.available();
// 从 PDM 麦克风读取数据到样本缓冲区
PDM.read(sampleBuffer, bytesAvailable);
// 16 位样本,每个样本占 2 字节
samplesRead = bytesAvailable / 2;
}
3. 代码说明
PDM.begin(1, 16000):启动 PDM 麦克风,单声道,采样率为 16kHz。
PDM.onReceive(onPDMdata):注册一个回调函数,当有数据时调用。
sampleBuffer[]:用于存储音频样本数据。
Serial.println(sampleBuffer):通过串口打印采样数据。
4. 查看波形数据
将代码上传到 Arduino Nano RP2040 Connect。
打开 串口监视器,设置波特率为 9600。
使用 Arduino IDE 串口绘图器(Serial Plotter),观察音频数据的波形变化。
5. 调试建议
调整采样率:通过修改 PDM.begin() 的第二个参数来提高或降低采样率。
滤波处理:可在 loop() 中增加滤波或降噪处理,平滑音频波形。
-
发表了主题帖:
【Follow me第二季第4期】任务二 学习IMU基础知识,通过串口打印六轴原始数据
Arduino Nano RP2040 Connect 板子上集成了 LSM6DSOX IMU 传感器,它可以测量三轴加速度和三轴角速度。以下是学习和调试 LSM6DSOX 的详细步骤。
硬件概述:
LSM6DSOX 是一款 6 轴 IMU 传感器,包含:
三轴加速度计(单位:m/s²)
三轴陀螺仪(单位:°/s 或 rad/s)
Accelerometer
Gyroscope
2. 安装所需库:
打开 Arduino IDE。
进入 工具 > 库管理器。
搜索并安装 Arduino_LSM6DSOX 库。
3. 示例代码:
下面是一个简单的示例,演示如何读取加速度和陀螺仪数据,并通过串口打印输出:
C++
#include <Arduino_LSM6DSOX.h>
void setup() {
Serial.begin(115200); // 初始化串口
while (!Serial); // 等待串口连接
if (!IMU.begin()) {
Serial.println("无法初始化 LSM6DSOX IMU 传感器!");
while (1);
}
Serial.println("LSM6DSOX IMU 传感器已初始化");
}
void loop() {
float ax, ay, az; // 加速度
float gx, gy, gz; // 角速度
// 读取加速度值
if (IMU.accelerationAvailable()) {
IMU.readAcceleration(ax, ay, az);
Serial.print("加速度 (m/s^2): X=");
Serial.print(ax);
Serial.print(" Y=");
Serial.print(ay);
Serial.print(" Z=");
Serial.println(az);
}
// 读取角速度值
if (IMU.gyroscopeAvailable()) {
IMU.readGyroscope(gx, gy, gz);
Serial.print("陀螺仪 (rad/s): X=");
Serial.print(gx);
Serial.print(" Y=");
Serial.print(gy);
Serial.print(" Z=");
Serial.println(gz);
}
delay(500); // 延迟 500 毫秒
}
代码说明:
IMU.begin():初始化 IMU 传感器。
IMU.readAcceleration():读取 X、Y、Z 三轴加速度值。
IMU.readGyroscope():读取 X、Y、Z 三轴角速度值。
5. 上传并查看数据:
将代码上传到 Arduino Nano RP2040 Connect。
打开 串口监视器,设置波特率为 9600。
观察串口输出的六轴原始数据。
6. 调试建议:
如果数据不稳定,可以调整延迟时间或尝试增加滤波算法。
使用滤波技术(如卡尔曼滤波)来平滑数据。
-
发表了主题帖:
【Follow me第二季第4期】任务一 搭建环境&Blink三色LED / 串口打印
本帖最后由 Netseye 于 2024-12-6 15:34 编辑
搭建开发环境:
下载并安装 Arduino IDE(确保使用最新版本)。
打开 Arduino IDE,进入 工具 > 板子 > 开发板管理器,搜索并安装 Arduino Mbed OS RP2040 或 Arduino Nano RP2040 Connect 的支持包。
连接 Arduino Nano RP2040 Connect 到电脑,通过 工具 > 端口,选择正确的端口。
2. Blink 三色 LED:
How can I use the embedded RGB LED?
RGB: The RGB LEDs are connected through the Wi-Fi module, so it is required to include the WiFiNINA library to use it.
使用板载的 RGB LED 控制三种颜色的闪烁(红、绿、蓝)。板子上还有一个LED_BUILTIN的橘色led 也一同点亮
C++
#include <WiFiNINA.h>
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
pinMode(LEDR, OUTPUT); // 红色 LED
pinMode(LEDG, OUTPUT); // 绿色 LED
pinMode(LEDB, OUTPUT); // 蓝色 LED
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
delay(500);
digitalWrite(LEDR, HIGH); // 红色亮起
delay(500);
digitalWrite(LEDR, LOW);
digitalWrite(LEDG, HIGH); // 绿色亮起
delay(500);
digitalWrite(LEDG, LOW);
digitalWrite(LEDB, HIGH); // 蓝色亮起
delay(500);
digitalWrite(LEDB, LOW);
}
[localvideo]c5ec6e394b334757fbcb1e03a2ec2da0[/localvideo]
3. 串口打印“Hello DigiKey & EEWorld!”:
结合 Blink 程序,在串口输出文本。
C++
#include <WiFiNINA.h>
void setup() {
Serial.begin(9600); // 初始化串口通信
while (!Serial) {} // 等待串口连接
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
pinMode(LEDR, OUTPUT); // 红色 LED
pinMode(LEDG, OUTPUT); // 绿色 LED
pinMode(LEDB, OUTPUT); // 蓝色 LED
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
delay(500);
digitalWrite(LEDR, HIGH); // 红色亮起
delay(500);
digitalWrite(LEDR, LOW);
digitalWrite(LEDG, HIGH); // 绿色亮起
delay(500);
digitalWrite(LEDG, LOW);
digitalWrite(LEDB, HIGH); // 蓝色亮起
delay(500);
digitalWrite(LEDB, LOW);
Serial.println("Hello DigiKey & EEWorld!"); // 打印信息
}
4. 上传代码并查看效果:
点击 上传 按钮,将代码上传到开发板。
打开 串口监视器,选择波特率 9600,观察输出 "Hello DigiKey & EEWorld!"。
-
加入了学习《基于ESP32-S3-LCD-EV-Board的物联网多功能平台》,观看 基于ESP32-S3-LCD-EV-Board的物联网多功能平台
- 2024-11-28
-
回复了主题帖:
【2024 DigiKey 创意大赛】基于ESP32-S3-LCD-EV-Board的物联网多功能平台
优秀作品
- 2024-11-26
-
加入了学习《基于Arduino玩转pico RP2040》,观看 基于Arduino玩转pico RP2040
- 2024-11-25
-
发表了主题帖:
【Follow me第二季第4期】Arduino_Nano_Connect板子开箱
本帖最后由 Netseye 于 2024-11-25 12:49 编辑
感谢DigiKey联合EEWorld组织的Follow me第二季第4期 ,本次选择了一下元件参与活动.
商品列表
ARDUINO NANO RP2040 CONNECT
文档: https://content.arduino.cc/assets/ABX00053-datasheet.pdf
ARDUINO NANO GROVE SHIELD
文档:https://mm.digikey.com/Volume0/opasdata/d220001/medias/docus/317/103100124_Web.pdf
YELLOW&BLUE DISPLAY GROVE OLED
文档: https://wiki.seeedstudio.com/cn/Grove-OLED-Yellow&Blue-Display-0.96-SSD1315_V1.0/
商品展示
上手体验
点亮屏幕
pdm音频展示
视频展示
[localvideo]dd0fa4129c64074a110da6fd7cee56e6[/localvideo]
[localvideo]b4555d6872e8a94238ade0d8fb183495[/localvideo]
ok大概这么多.后续详在详细介绍.
- 2024-10-31
-
加入了学习《【2024 DigiKey创意大赛】- 基于毫米波雷达的生命体征检测及健康监护系统》,观看 【2024 DigiKey创意大赛】- 基于毫米波雷达的生命体征检测及健康监护系统-作品提交
- 2024-10-28
-
发表了主题帖:
【2024 DigiKey创意大赛】智能家居控制中心
本帖最后由 Netseye 于 2024-11-25 14:33 编辑
一、作品简介
本次作品使用ESP32-S3-LCD-EV-BOARD 和STEMMA QT BME680 SENSOR BOARD BME680 来实现智能家居相关的一些尝试.
主要实现功能基于ESP32-S3-LCD-EV-BOARD实现esp-rainmaker 二维码配网.
通过bme680来获取室内气体,湿度,压力,温度 数据显示在屏幕上.并定时同步到ESP RAINMAKER.bme680温度超过30 会通过esp-rainker发送高温预警通知.
并通过esp-rainker实现数据上报. bme680温度超过30 会通过esp-rainker发送高温预警通知.
实现通过网络获取室外温度显示在屏幕上.并且可以通过语音指令进行开关灯控制.和室内温湿度等循环
二、系统框图、
文件目录
3185 ◯ tree -L 2
.
├── CMakeLists.txt
├── README.md
├── bsp
│ └── bsp_extra // esp32 bsp extra
├── components
│ ├── app_insights // ESP Insights (Beta)
│ ├── app_network // 网络配置相关库
│ ├── app_reset // 重置相关库提供给esp-rmaker使用
│ ├── bme68x_lib // BME68X传感器驱动
│ ├── gpio_button // 按键驱动
│ ├── i2c_bus // I2C总线驱动
│ ├── nimble_central_utils // NimBLE工具库
│ └── qrcode // 二维码库
├── dependencies.lock
├── esp_tts_voice_data_xiaoxin.dat // tts语音数据
├── main // 项目主目录
│ ├── CMakeLists.txt
│ ├── Kconfig
│ ├── app // 项目应用层逻辑代码
│ ├── gui // 项目GUI代码
│ ├── idf_component.yml // 项目组件依赖配置
│ ├── main.c // 项目入口
│ ├── main.h
│ ├── rmaker // esp-rmaker相关代码
│ ├── settings.c // 项目配置
│ └── settings.h
├── partitions.csv // 分区表
├── sdkconfig
├── sdkconfig.defaults
├── sdkconfig.defaults.psram_120m_ddr
├── sdkconfig.old
└── spiffs // spiffs文件系统存音频文件和图片
├── echo_cn_end.wav
├── echo_cn_ok.wav
├── echo_cn_wake.wav
├── echo_en_end.wav
├── echo_en_ok.wav
├── echo_en_wake.wav
├── gpt_avatar.gif
└── mp3
18 directories, 23 files
核心代码
3193 ◯ tree -L 2
.
├── CMakeLists.txt
├── Kconfig
├── app
│ ├── app_audio.c
│ ├── app_audio.h
│ ├── app_ble.c
│ ├── app_ble.h
│ ├── app_chat.c
│ ├── app_chat.h
│ ├── app_sr.c
│ ├── app_sr.h
│ ├── app_sr_handler.c
│ ├── app_sr_handler.h
│ ├── app_sr_tts.c
│ ├── app_sr_tts.h
│ ├── app_weather.c
│ ├── app_weather.h
│ └── blecent.h
├── gui
│ ├── assert
│ ├── font
│ ├── lv_example_image.h
│ ├── lv_example_pub.c
│ ├── lv_example_pub.h
│ ├── lv_example_top_menu.c
│ ├── lv_schedule_basic.c
│ ├── lv_schedule_basic.h
│ ├── ui_chat.c
│ ├── ui_chat.h
│ ├── ui_rmaker.c
│ ├── ui_rmaker.h
│ ├── ui_sr.c
│ └── ui_sr.h
├── idf_component.yml
├── main.c
├── main.h
├── rmaker
│ ├── app_bme680.c
│ ├── app_bme680.h
│ ├── app_driver.c
│ ├── app_priv.h
│ ├── app_rmaker.c
│ └── app_rmaker.h
├── settings.c
└── settings.h
三、各部分功能说明
Main
void app_main(void)
{
/* Initialize NVS. */
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
ESP_ERROR_CHECK(settings_read_parameter_from_nvs());
bsp_spiffs_mount();
bsp_i2c_init();
bsp_display_start();
bsp_extra_led_init();
bsp_extra_codec_init();
app_bme680_init();
ESP_LOGI(TAG, "Display LVGL");
bsp_display_lock(0);
lv_style_pre_init();
lv_create_home(&rmaker_Layer);
bsp_display_unlock();
ESP_LOGI(TAG, "Initializing RainMaker");
app_rmaker_start();
app_weather_start();
ESP_LOGI(TAG, "speech recognition Enable");
app_sr_start(false);
bsp_audio_poweramp_enable(true);
ESP_LOGI(TAG, "TTS Enable");
app_tts_init();
}
BME680数据采集
// 初始化i2c bme680
void app_bme680_init(void) {
// 初始化 I2C 总线
esp_err_t ret = i2c_bus_init(
&i2c_bus, BSP_I2C_NUM, BSP_I2C_SDA_R16, BSP_I2C_SCL_R16,
GPIO_PULLUP_DISABLE, GPIO_PULLUP_DISABLE, CONFIG_BSP_I2C_CLK_SPEED_HZ);
// 初始化 bme68x_lib_t 实例
esp_err_t result = bme68x_lib_init(&bme68x_instance, &i2c_bus, intf);
if (result != ESP_OK) {
ESP_LOGE(TAG, "BME680 initialization failed");
} else {
ESP_LOGI(TAG, "BME680 initialized successfully");
}
bme68x_lib_set_filter(&bme68x_instance, BME68X_FILTER_OFF);
bme68x_lib_set_tph(&bme68x_instance, BME68X_OS_2X, BME68X_OS_1X,
BME68X_OS_16X);
bme68x_lib_set_heater_prof_for(&bme68x_instance, 300, 100);
bme68x_lib_set_op_mode(&bme68x_instance, ope_mode);
}
// 获取bme680信息
esp_err_t app_bme680_get_current_info(bme68x_data_t *info) {
if (bme68x_lib_intf_error(&bme68x_instance) == 0) {
bme68x_lib_set_op_mode(&bme68x_instance, ope_mode);
bme_delay = bme68x_lib_get_meas_dur(&bme68x_instance, ope_mode);
(bme68x_instance.bme6).delay_us(bme_delay, (bme68x_instance.bme6).intf_ptr);
bme68x_lib_fetch_data(&bme68x_instance);
bme68x_data_t *data = bme68x_lib_get_all_data(&bme68x_instance);
info->temperature = data->temperature;
info->pressure = data->pressure;
info->humidity = data->humidity;
info->gas_resistance = data->gas_resistance;
info->status = data->status;
return ESP_OK;
} else {
return ESP_FAIL;
}
}
ESP RainMaker
/* Create a Switch device and add the relevant parameters to it */
switch_device =
esp_rmaker_switch_device_create("开关", NULL, DEFAULT_SWITCH_POWER);
esp_rmaker_device_add_cb(switch_device, write_cb, NULL);
esp_rmaker_node_add_device(node, switch_device);
/* Create a Light device and add the relevant parameters to it */
light_device =
esp_rmaker_lightbulb_device_create("灯", NULL, DEFAULT_LIGHT_POWER);
esp_rmaker_device_add_cb(light_device, write_cb, NULL);
esp_rmaker_device_add_param(light_device, esp_rmaker_brightness_param_create(
ESP_RMAKER_DEF_BRIGHTNESS_NAME,
DEFAULT_LIGHT_BRIGHTNESS));
esp_rmaker_param_t *color_param = esp_rmaker_param_create("color", NULL, esp_rmaker_int(0), PROP_FLAG_READ | PROP_FLAG_WRITE);
esp_rmaker_param_add_ui_type(color_param, ESP_RMAKER_UI_HUE_SLIDER);
esp_rmaker_device_add_param(light_device, color_param);
esp_rmaker_device_add_attribute(light_device, "Desc", "2024 DigiKey");
esp_rmaker_node_add_device(node, light_device);
/* Create a Fan device and add the relevant parameters to it */
fan_device = esp_rmaker_fan_device_create("风扇", NULL, DEFAULT_FAN_POWER);
esp_rmaker_device_add_cb(fan_device, write_cb, NULL);
esp_rmaker_device_add_param(fan_device,
esp_rmaker_speed_param_create(ESP_RMAKER_DEF_SPEED_NAME,
DEFAULT_FAN_SPEED)); esp_rmaker_node_add_device(node, fan_device);
//2 节点上: 创建一个BME680设备。
temp_sensor_device = esp_rmaker_temp_sensor_device_create("BME680", NULL, 0);
// // 接下来的几行代码创建了几个参数,如“地区”、“天气”、“风力”和“湿度”,并设置了它们的属性和初始值。
esp_rmaker_param_t *temperature_param = esp_rmaker_param_create("温度", "bme680.param.temperature", esp_rmaker_float(app_get_current_temperature()), PROP_FLAG_READ | PROP_FLAG_WRITE);
esp_rmaker_param_t *humidity_param = esp_rmaker_param_create("湿度", "bme680.param.humidity", esp_rmaker_float(app_get_current_humidity()), PROP_FLAG_READ | PROP_FLAG_WRITE);
esp_rmaker_param_t *pressure_param = esp_rmaker_param_create("气压", "bme680.param.pressure", esp_rmaker_float(app_get_current_pressure()), PROP_FLAG_READ | PROP_FLAG_WRITE);
esp_rmaker_param_t *altitude_param = esp_rmaker_param_create("海拔", "bme680.param.altitude", esp_rmaker_float(app_get_current_altitude()), PROP_FLAG_READ | PROP_FLAG_WRITE);
esp_rmaker_param_t *gas_param = esp_rmaker_param_create("GAS", "bme680.param.gas", esp_rmaker_float(app_get_current_gas()), PROP_FLAG_READ | PROP_FLAG_WRITE);
esp_rmaker_param_t *iaq_param = esp_rmaker_param_create("IAQ", "bme680.param.iaq", esp_rmaker_float(app_get_current_iaq()), PROP_FLAG_READ | PROP_FLAG_WRITE);
// // 这些参数随后被添加到温度传感器虚拟设备中。
esp_rmaker_device_add_param(temp_sensor_device, temperature_param);
esp_rmaker_device_add_param(temp_sensor_device, humidity_param);
esp_rmaker_device_add_param(temp_sensor_device, pressure_param);
esp_rmaker_device_add_param(temp_sensor_device, altitude_param);
esp_rmaker_device_add_param(temp_sensor_device, gas_param);
esp_rmaker_device_add_param(temp_sensor_device, iaq_param);
// // 设置主要参数 app上首先显示
esp_rmaker_device_assign_primary_param(temp_sensor_device, temperature_param);
// esp_rmaker_param_t *get_param = esp_rmaker_param_create("get_weather", NULL, esp_rmaker_bool(false), PROP_FLAG_READ | PROP_FLAG_WRITE);
// esp_rmaker_param_add_ui_type(get_param, ESP_RMAKER_UI_PUSHBUTTON);
// esp_rmaker_device_add_param(temp_sensor_device, get_param);
esp_rmaker_node_add_device(node, temp_sensor_device);
Esp-sr
命令列表
static const sr_cmd_t g_default_cmd_info[] = {
// Chinese
{SR_CMD_TEMP_DEC, SR_LANG_CN, 0, "室内温度", "shi nei wen du", {NULL}},
{SR_CMD_PRESSURE_DEC, SR_LANG_CN, 0, "室内气压", "shi nei qi ya", {NULL}},
{SR_CMD_HUMIDITY_DEC, SR_LANG_CN, 0, "室内湿度", "shi nei shi du", {NULL}},
{SR_CMD_LIGHT_ON, SR_LANG_CN, 0, "打开电灯", "da kai dian deng", {NULL}},
{SR_CMD_LIGHT_OFF, SR_LANG_CN, 0, "关闭电灯", "guan bi dian deng", {NULL}},
};
Esp-sr 初始化
BaseType_t ret_val;
models = esp_srmodel_init("model");
afe_handle = (esp_afe_sr_iface_t *)&ESP_AFE_SR_HANDLE;
afe_config_t afe_config = AFE_CONFIG_DEFAULT();
afe_config.wakenet_model_name = esp_srmodel_filter(models, ESP_WN_PREFIX, NULL);
afe_config.aec_init = false;
esp_afe_sr_data_t *afe_data = afe_handle->create_from_config(&afe_config);
g_sr_data->afe_handle = afe_handle;
g_sr_data->afe_data = afe_data;
sys_param_t *param = settings_get_parameter();
g_sr_data->lang = SR_LANG_MAX;
ret = app_sr_set_language(param->sr_lang);
esp-sr handler
void sr_handler_task(void *pvParam)
{
static lv_event_info_t event;
event.event = LV_EVENT_EXIT_CLOCK;
while (true) {
sr_result_t result;
app_sr_get_result(&result, portMAX_DELAY);
if (true == result.beep_enable) {
sr_echo_play(AUDIO_PRESS);
continue;
}
if (ESP_MN_STATE_TIMEOUT == result.state) {
#if !SR_RUN_TEST
event.event = LV_EVENT_I_AM_LEAVING;
app_lvgl_send_event(&event);
sr_echo_play(AUDIO_END);
#endif
continue;
}
if (WAKENET_DETECTED == result.wakenet_mode) {
event.event = LV_EVENT_I_AM_HERE;
app_lvgl_send_event(&event);
#if !SR_RUN_TEST
sr_echo_play(AUDIO_WAKE);
#endif
continue;
}
if (ESP_MN_STATE_DETECTED & result.state) {
const sr_cmd_t *cmd = app_sr_get_cmd_from_id(result.command_id);
if (NULL == cmd) {
continue;
}
ESP_LOGI(TAG, "command:%s, act:%d", cmd->str, cmd->cmd);
event.event_data = (void *)cmd->str;
switch (cmd->cmd) {
case SR_CMD_SET_RED:
event.event = LV_EVENT_LIGHT_RGB_SET;
app_lvgl_send_event(&event);
break;
case SR_CMD_SET_WHITE:
event.event = LV_EVENT_LIGHT_RGB_SET;
app_lvgl_send_event(&event);
break;
case SR_CMD_SET_YELLOW:
event.event = LV_EVENT_LIGHT_RGB_SET;
app_lvgl_send_event(&event);
break;
case SR_CMD_SET_BLUE:
event.event = LV_EVENT_LIGHT_RGB_SET;
app_lvgl_send_event(&event);
break;
case SR_CMD_LIGHT_ON:
event.event = LV_EVENT_LIGHT_ON;
app_lvgl_send_event(&event);
break;
case SR_CMD_LIGHT_OFF:
event.event = LV_EVENT_LIGHT_OFF;
app_lvgl_send_event(&event);
break;
case SR_CMD_TEMP_DEC:
char temp_str[64];
snprintf(temp_str, sizeof(temp_str), "室内当前 %.2f 摄氏度 ", info.temperature);
app_tts_play(temp_str);
break;
case SR_CMD_PRESSURE_DEC:
char pressure_str[64];
snprintf(pressure_str, sizeof(pressure_str), "室内当前 %.2f 千帕 ", info.pressure / 100.0);
app_tts_play(pressure_str);
break;
case SR_CMD_HUMIDITY_DEC:
char humidity_str[64];
snprintf(humidity_str, sizeof(humidity_str), "室内当前湿度 %.2f ", info.humidity);
app_tts_play(humidity_str);
break;
default:
ESP_LOGE(TAG, "Unknow cmd");
break;
}
#if !SR_RUN_TEST
// sr_echo_play(AUDIO_OK);
#endif
}
}
vTaskDelete(NULL);
}
Esp-tts
esp_err_t app_tts_init(void) {
/* Initial voice set from separate voice data partition */
const esp_partition_t* part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "voice_data");
if (part==NULL) {
ESP_LOGE(TAG, "Couldn't find voice data partition!");
return 0;
} else {
ESP_LOGI(TAG, "voice_data paration size:%ld", part->size);
}
const void* voicedata;
esp_partition_mmap_handle_t mmap;
esp_err_t err = esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_DATA, &voicedata, &mmap);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Couldn't map voice data partition!");
return ESP_FAIL;
}
esp_tts_voice_t *voice = esp_tts_voice_set_init(&esp_tts_voice_template, (int16_t*)voicedata);
tts_handle = esp_tts_create(voice);
if (tts_handle == NULL) {
ESP_LOGE(TAG, "Failed to create TTS handle!");
return ESP_FAIL;
}
spk_codec_dev = bsp_audio_codec_speaker_init();
assert(spk_codec_dev);
esp_codec_dev_set_out_vol(spk_codec_dev, DEFAULT_VOLUME);
return ESP_OK;
}
esp_err_t app_tts_play(const char *prompt1)
{
esp_codec_dev_sample_info_t fs = {
.sample_rate = SAMPLE_RATE,
.channel = EXAMPLE_CHANNEL,
.bits_per_sample = EXAMPLE_BITS,
};
esp_codec_dev_open(spk_codec_dev, &fs);
/* Play prompt text */
ESP_LOGI(TAG, "prompt1: %s", prompt1);
if (esp_tts_parse_chinese(tts_handle, prompt1)) {
int len[1]={0};
do {
short *pcm_data=esp_tts_stream_play(tts_handle, len, 1);
int error_code = esp_codec_dev_write(spk_codec_dev, pcm_data, len[0]*2);
// ESP_LOGI(TAG, "Write result: %d ", error_code);
// ESP_LOGI(TAG, "data: %d", len[0]);
} while(len[0]>0);
}
esp_codec_dev_close(spk_codec_dev);
esp_tts_stream_reset(tts_handle);
return ESP_OK;
}
四、作品源码
iot-box.zip
五、作品功能演示视频
[localvideo]b9870e2701dad886393917ba283ef82e[/localvideo]
六、项目总结
https://bbs.eeworld.com.cn/thread-1291239-1-1.html
https://bbs.eeworld.com.cn/thread-1292908-1-1.html
https://bbs.eeworld.com.cn/thread-1296188-1-1.html
ESP32-S3-LCD-EV-BOARD 和 bme680 是非常优秀的开发板和传感器.本次项目抛砖引玉,仅仅是为了完整在乐鑫生态下的iot探索,并未能完全展示其在IOT上的应用.本来计划是有对应的llm大模型对话和ble链接米家蓝牙设备的.尝试过程中存在一些性能和冲突问题.暂时未能解决.调试过程较为痛苦.完整刷一次固件要好几分钟...
七、其他
后续会对代码进行优化和整理.解决大模型对话和ble接入米家设备的集成问题.使项目更加完善.
-
上传了资料:
【2024 DigiKey创意大赛】智能家居控制中心
- 2024-10-20
-
加入了学习《得捷电子专区》,观看 【2024 DigiKey 创意大赛】红外温度检测及火灾报警器
- 2024-10-15
-
回复了主题帖:
【2024 DigiKey创意大赛】【智能家居控制中心】【EP03】ESP32-S3-LCD-EV + BME680 BLE
后记
关于如何发现米家蓝牙设备地址和关于idf中的数据大小端问题做个总结:
如何发现米家设备:
https://github.com/rytilahti/python-miio
通过手机ble app 找名字LYWSD03MMC或者自己写一个工具
https://lywsd02mmc.bilaldurrani.com/类似的一些web端的ble工具
其他...就不列举了
大小端转换问题:
我这边写了一个小工具来实现
#include <stdint.h>
#include <stdio.h>
#include <string.h>
void hexStringToByteArray(const char *hexString, uint8_t *byteArray) {
size_t len = strlen(hexString);
for (size_t i = 0; i < len; i += 2) {
sscanf(hexString + i, "%2hhx", &byteArray[i / 2]);
}
}
void printByteArray(const uint8_t *byteArray) {
printf("BLE_UUID128_DECLARE(");
for (size_t i = 15; i < 16; i--) {
printf("0x%02x", byteArray[i]);
if (i > 0) {
printf(", ");
}
}
printf(")\n");
}
int main() {
const char *hexStrings[] = {
"ebe0ccb07a0a4b0c8a1a6ff2997da3a6",
"ebe0ccc17a0a4b0c8a1a6ff2997da3a6"
};
uint8_t byteArray[16]; // 32 hex digits = 16 bytes
for (size_t j = 0; j < sizeof(hexStrings) / sizeof(hexStrings[0]); j++) {
hexStringToByteArray(hexStrings[j], byteArray);
printf("Little-endian Byte array for %s: ", hexStrings[j]);
printByteArray(byteArray);
}
return 0;
}
-
发表了主题帖:
【2024 DigiKey创意大赛】【智能家居控制中心】【EP03】ESP32-S3-LCD-EV + BME680 BLE
本次实现通过ESP32 BLE 来实现链接米家的温湿度计.esp32 存在2个蓝牙协议栈. Bluedroid - Dual-mode和 NimBLE - BLE only二选一. 最早使用了Bluedroid来实现这个功能.集成过程中发现esp-rainmarker 使用的是NimBLE,又不是一波令人窒息的操作...
NimBLE: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/nimble/index.html
Bluedroid - Dual-mode: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/bt_le.html
概念
Gap
GAP 的广播工作流程
GAP(Generic Access Profile,通用访问配置文件)是蓝牙协议栈中的一部分,负责定义蓝牙设备之间如何发现、连接和进行交互。GAP提供了设备连接和通信的基本框架。它主要包含以下几个方面:
1. 广播和扫描
广播(Advertising):设备通过广播信道发送数据,其他设备可以扫描到这些广播数据。广播包含了设备的基本信息,例如名称、服务UUID等。
扫描(Scanning):设备监听广播信道以发现周围的蓝牙设备和其广播数据。
2. 连接
发起连接(Connection Initiation):扫描设备可以发起与广播设备的连接,建立一条点对点的连接。
连接参数(Connection Parameters):GAP定义了在连接建立时如何设置连接参数,如连接间隔、超时时间等。
3. 角色
GAP规定了蓝牙设备可以扮演的四种角色:
广播者(Broadcaster):只广播数据,不接收数据或发起连接。
观察者(Observer):只扫描广播数据,不发起连接。
主设备(Central):扫描并发起连接,通常是主控设备(如手机)。
从设备(Peripheral):广播并接受连接请求,通常是外围设备(如传感器)。
4. 配对与安全
配对(Pairing):GAP定义了蓝牙设备如何通过加密进行配对,确保通信的安全性。
密钥分发(Key Distribution):配对后,设备之间可以交换密钥用于加密通信。
安全等级(Security Levels):GAP支持不同的安全等级,从没有加密到加密和认证。
5. 设备名称和可见性
设备名称(Device Name):GAP允许设备广播其名称,以便用户识别。
可见性(Visibility):设备可以配置为可见或不可见,影响它是否能够被其他设备发现。
6. 连接管理
连接建立与终止:GAP定义了设备如何建立和终止蓝牙连接。
连接参数更新(Connection Parameter Update):在连接过程中,设备可以协商更新连接参数以优化性能。
7. 扩展功能
扩展广告(Extended Advertising):如你提到的,GAP标准中的一部分允许广播更多的数据,支持长时间的广播周期。
周期性广告(Periodic Advertising):用于周期性发送广播数据,适用于广播数据不频繁更新的场景。
GAP是设备发现、连接建立、连接管理、安全配对的基础层,支持BLE应用的各种交互方式。
GATT
(GATT服务端)和中心设备(GATT客户端)之间的数据交流流程,
GATT(Generic Attribute Profile,通用属性配置文件)是蓝牙低功耗(BLE)协议栈中的一个重要部分,负责设备之间数据的组织、交换和传输。它基于属性(Attribute)的形式进行通信,定义了如何在已连接的设备之间通过服务和特征(Characteristics)进行数据传输。GATT的设计使得BLE设备能够以结构化的方式进行数据交互,主要包括以下内容:
1. 属性(Attribute)
属性是GATT通信的核心,表示设备内部的某一项数据,通常由一个16位或128位的UUID标识。属性包含三个关键元素:
句柄(Handle):属性的唯一标识符,通常是一个16位的值,表示属性在设备上的位置。
类型(Type):属性的类型,通过UUID进行标识。例如,某属性可能是一个服务或特征。
值(Value):属性的实际数据,可以是任意数据类型。
2. 服务(Service)
服务是由一组相关的特征和属性组成的,代表一个逻辑功能。例如,心率服务可以包含多个特征来传输心率数据。
服务类型:
主要服务(Primary Service):表示设备的主要功能服务。
次要服务(Secondary Service):依附于主要服务的辅助功能。
3. 特征(Characteristic)
特征是GATT的核心数据单元,每个特征由一个值和可选的描述符组成。特征用于读写或通知数据传输。
特征值(Characteristic Value):特征的具体数据,可以被读取、写入或订阅。
特征属性(Characteristic Properties):定义特征支持的操作类型,如读取、写入、通知等。
4. 描述符(Descriptor)
描述符是特征的附加信息,描述特征的特定细节。例如,描述符可以定义特征值的单位或数据格式。
常见的描述符包括:
客户端特征配置描述符(Client Characteristic Configuration Descriptor, CCCD):用于配置通知或指示。
特征用户描述符(Characteristic User Description, CUD):对特征值的简要说明。
5. GATT 操作
GATT定义了设备如何通过服务和特征来交互数据,包括以下操作:
发现操作(Discovery Operations):设备可以发现远程设备支持的服务、特征和描述符。这包括发现所有服务、发现特定特征、发现特征的描述符等。
读取操作(Read Operations):读取某个特征或描述符的值,通常主设备(Central)向从设备(Peripheral)发起。
写入操作(Write Operations):写入某个特征或描述符的值,可以是有响应的写入(Write with Response)或无响应的写入(Write without Response)。
通知(Notification)和指示(Indication):从设备可以主动向主设备发送特征值的变化。通知不需要确认,指示则需要主设备确认接收。
6. GATT 服务协议栈
GATT作为一个协议栈分层,通常与其他蓝牙层次协同工作:
ATT(Attribute Protocol):GATT基于ATT协议构建,ATT提供了对设备属性进行读写的机制。GATT扩展了ATT的功能,加入了服务和特征的结构化数据模型。
L2CAP(Logical Link Control and Adaptation Protocol):ATT和GATT协议通过L2CAP层进行数据封装和传输。
7. GATT 角色
GATT协议中,设备可以扮演以下两种角色:
GATT 服务器(Server):保存数据并响应客户端的请求,通常是外围设备(如传感器)。
GATT 客户端(Client):发起请求来读取或写入数据,通常是主设备(如手机或平板)。
8. 标准化服务和特征
蓝牙组织定义了一系列标准化的服务和特征,用于特定应用场景。例如:
心率服务(Heart Rate Service):用于传输心率数据。
电池服务(Battery Service):报告设备的电池电量。
设备信息服务(Device Information Service, DIS):提供设备的制造商信息、硬件版本等。
9. 长特征值
GATT支持通过分块操作(Read Blob/Write Blob)来传输超出单个ATT帧限制的长特征值(通常大于20字节)。
10. GATT 协议操作示例
连接后发现服务:在连接建立后,客户端通常会首先发现服务器支持的服务。
读取心率数据:客户端可以读取“心率特征”值,获取实时心率。
写入控制命令:客户端可以通过写入特定特征来控制设备的行为。
GATT 结构
GATT事务是建立在嵌套的Profiles,Services和Characteristics之上的,如下如所示:
总结来说,GATT负责组织和传输设备之间的属性数据,通过服务和特征的形式,使BLE设备能够以标准化的方式进行数据交互。
实践
首先我们需要通过GAP协议进行扫描和链接,在ble 5.0以上存在着Advertising 和 Extended Advertising两种不同的方式.可以通过 idf.py menuconfig 进行修改Enable extended advertising esp32芯片的板子就不要尝试了,用不了.
(Top) → Component config → Bluetooth → NimBLE Options → Enable BLE 5 feature
Espressif IoT Development Framework Configuration
[*] Enable 2M Phy
[*] Enable coded Phy
[ ] Enable extended advertising
(0) Maximum number of periodic advertising syncs
[ ] Enable GATT caching ----
开启蓝牙
void app_ble_init(void) {
int rc;
/* Initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ret = nimble_port_init();
if (ret != ESP_OK) {
ESP_LOGE(tag, "Failed to init nimble %d ", ret);
return;
}
ESP_LOGI(tag, "init nimble success");
/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble-blecent");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(blecent_host_task);
}
实现cb_event和开启扫描
/**
* Initiates the GAP general discovery procedure.
*/
static void blecent_scan(void) {
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
int rc;
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
return;
}
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
disc_params.filter_duplicates = 1;
/**
* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
disc_params.passive = 0;
/* Use defaults for the rest of the parameters. */
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
blecent_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error initiating GAP discovery procedure; rc=%d\n", rc);
}
}
static void blecent_on_reset(int reason) {
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
static void blecent_on_sync(void) {
int rc;
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Begin scanning for a peripheral to connect to. */
blecent_scan();
}
void blecent_host_task(void *param) {
ESP_LOGI(tag, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
nimble_port_freertos_deinit();
}
/* This function showcases stack init and deinit procedure. */
static void stack_init_deinit(void) {
int rc;
while (1) {
vTaskDelay(1000);
ESP_LOGI(tag, "Deinit host");
rc = nimble_port_stop();
if (rc == 0) {
nimble_port_deinit();
} else {
ESP_LOGI(tag, "Nimble port stop failed, rc = %d", rc);
break;
}
vTaskDelay(1000);
ESP_LOGI(tag, "Init host");
rc = nimble_port_init();
if (rc != ESP_OK) {
ESP_LOGI(tag, "Failed to init nimble %d ", rc);
break;
}
nimble_port_freertos_init(blecent_host_task);
ESP_LOGI(tag, "Waiting for 1 second");
}
}
链接米家温湿度计
温度计定义
/*** The UUID of the service containing the subscribable characterstic ***/
static const ble_uuid_t *remote_svc_uuid =
BLE_UUID128_DECLARE(0xa6, 0xa3, 0x7d, 0x99, 0xf2, 0x6f, 0x1a, 0x8a, 0x0c,
0x4b, 0x0a, 0x7a, 0xb0, 0xcc, 0xe0, 0xeb);
/*** The UUID of the subscribable chatacteristic ***/
static const ble_uuid_t *remote_chr_uuid =
BLE_UUID128_DECLARE(0xa6, 0xa3, 0x7d, 0x99, 0xf2, 0x6f, 0x1a, 0x8a, 0x0c,
0x4b, 0x0a, 0x7a, 0xc1, 0xcc, 0xe0, 0xeb);
static uint8_t target_addr[6] = {0xE1, 0x0A, 0x04, 0x38, 0xC1, 0xA4};
typedef struct {
float temperature;
float humidity;
float voltage;
float battery;
} Result;
Result processBuffer(const uint8_t *buff) {
Result result = {0, 0, 0, 0};
// Temperature conversion
int16_t temp = (int16_t)(buff[0] | (buff[1] << 8)); // Little-endian
result.temperature = temp / 100.0f;
// Humidity conversion
result.humidity = (float)buff[2];
// Voltage conversion
int16_t voltageRaw = (int16_t)(buff[3] | (buff[4] << 8)); // Little-endian
result.voltage = voltageRaw / 1000.0f;
// Battery percentage calculation
result.battery = ((result.voltage - 2) / (3.261 - 2)) * 100;
result.battery = (result.battery < 0) ? 0
: (result.battery > 100)
? 100
: result.battery; // Clamp between 0 and 100
return result;
}
BLE读取信息
static int blecent_on_read(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg) {
MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error->status,
conn_handle);
if (error->status == 0) {
MODLOG_DFLT(INFO, " attr_handle=%d value=", attr->handle);
print_mbuf(attr->om);
Result result = processBuffer(attr->om->om_data);
printf("Temperature: %.2f\n", result.temperature);
printf("Humidity: %.2f\n", result.humidity);
printf("Voltage: %.3f\n", result.voltage);
printf("Battery: %.2f%%\n", result.battery);
}
MODLOG_DFLT(INFO, "\n");
return 0;
}
static void blecent_read_write_subscribe(const struct peer *peer) {
const struct peer_chr *chr;
int rc;
/* Read the supported-new-alert-category characteristic. */
chr = peer_chr_find_uuid(
peer,
remote_svc_uuid, remote_chr_uuid);
if (chr == NULL) {
MODLOG_DFLT(ERROR, "Error \n");
goto err;
}
rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle, blecent_on_read,
NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to read characteristic; rc=%d\n", rc);
goto err;
}
return;
err:
/* Terminate the connection. */
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
static void blecent_on_disc_complete(const struct peer *peer, int status,
void *arg) {
if (status != 0) {
/* Service discovery failed. Terminate the connection. */
MODLOG_DFLT(ERROR,
"Error: Service discovery failed; status=%d "
"conn_handle=%d\n",
status, peer->conn_handle);
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return;
}
/* Service discovery has completed successfully. Now we have a complete
* list of services, characteristics, and descriptors that the peer
* supports.
*/
MODLOG_DFLT(INFO,
"Service discovery complete; status=%d "
"conn_handle=%d\n",
status, peer->conn_handle);
/* Now perform three GATT procedures against the peer: read,
* write, and subscribe to notifications for the ANS service.
*/
blecent_read_write_subscribe(peer);
}
static int blecent_gap_event(struct ble_gap_event *event, void *arg) {
struct ble_gap_conn_desc desc;
struct ble_hs_adv_fields fields;
#if MYNEWT_VAL(BLE_HCI_VS)
#if MYNEWT_VAL(BLE_POWER_CONTROL)
struct ble_gap_set_auto_pcl_params params;
#endif
#endif
int rc;
switch (event->type) {
case BLE_GAP_EVENT_DISC:
rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
event->disc.length_data);
if (rc != 0) {
return 0;
}
/* An advertisment report was received during GAP discovery. */
print_adv_fields(&fields);
/* Try to connect to the advertiser if it looks interesting. */
blecent_connect_if_interesting(&event->disc);
return 0;
case BLE_GAP_EVENT_EXT_DISC:
/* An advertisment report was received during GAP discovery. */
ext_print_adv_report(&event->disc);
blecent_connect_if_interesting(&event->disc);
return 0;
default:
return 0;
}
}
结果:
优化
目前提供的是代码核心部分和简单的测试逻辑.项目中会做成可以手动控制扫码设备.通过触摸屏点击对应的蓝牙设备进行连接.并支持多个设备连接.
- 2024-09-19
-
回复了主题帖:
【2024 DigiKey创意大赛】【智能家居控制中心】【EP02】ESP32-S3-LCD-EV + BME680整合
oet 发表于 2024-9-19 10:23
可以试试PlatformIO+Arduino 框架,这个现成的轮子比较多,移植起来省事
感谢..
-
回复了主题帖:
【2024 DigiKey创意大赛】【智能家居控制中心】【EP02】ESP32-S3-LCD-EV + BME680整合
damiaa 发表于 2024-9-19 09:14
ESP32-S3-LCD-EV-BOARD 确实不错。楼主选材思路正确。