Skip to main content

Seeed Studio XIAO MG24 Sense 内置传感器使用指南

XIAO MG24 Sense IMU

内置传感器概述

6轴 IMU(惯性测量单元) 传感器如 LSM6DS3TR-C 集成了加速度计和陀螺仪,用于测量物体在三维空间中的运动和方向。具体来说,LSM6DS3TR-C 具有以下特性:

加速度计功能:

  • 测量物体沿 X、Y 和 Z 轴的加速度。它能够感知物体运动(例如,静止、加速、减速)和倾斜变化(例如,物体的角度)。
  • 可用于检测步态、位置变化、振动等。

陀螺仪功能:

  • 测量物体围绕 X、Y 和 Z 轴的角速度,即物体的旋转。
  • 可用于检测旋转、旋转速率和方向变化。
  • X轴角度(Roll) 是围绕 X 轴旋转方向的角度。
  • Y轴角度(Pitch) 是围绕 Y 轴旋转方向的角度。
  • Z轴角度(Yaw) 是围绕 Z 轴旋转方向的角度。

软件准备

点击 github 下载链接获取六轴传感器驱动库。

代码实现


#include <LSM6DS3.h>
#include <Wire.h>

//Create a instance of class LSM6DS3
LSM6DS3 myIMU(I2C_MODE, 0x6A); //I2C device address 0x6A
float aX, aY, aZ, gX, gY, gZ;
const float accelerationThreshold = 2.5; // threshold of significant in G's
const int numSamples = 119;
int samplesRead = numSamples;

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
while (!Serial);

pinMode(PD5,OUTPUT);
digitalWrite(PD5,HIGH);
//Call .begin() to configure the IMUs
if (myIMU.begin() != 0) {
Serial.println("Device error");
} else {
Serial.println("aX,aY,aZ,gX,gY,gZ");
}
}

void loop() {
// wait for significant motion
while (samplesRead == numSamples) {
// read the acceleration data
aX = myIMU.readFloatAccelX();
aY = myIMU.readFloatAccelY();
aZ = myIMU.readFloatAccelZ();

// sum up the absolutes
float aSum = fabs(aX) + fabs(aY) + fabs(aZ);

// check if it's above the threshold
if (aSum >= accelerationThreshold) {
// reset the sample read count
samplesRead = 0;
break;
}
}

// check if the all the required samples have been read since
// the last time the significant motion was detected
while (samplesRead < numSamples) {
// check if both new acceleration and gyroscope data is
// available
// read the acceleration and gyroscope data

samplesRead++;

// print the data in CSV format
Serial.print(myIMU.readFloatAccelX(), 3);
Serial.print(',');
Serial.print(myIMU.readFloatAccelY(), 3);
Serial.print(',');
Serial.print(myIMU.readFloatAccelZ(), 3);
Serial.print(',');
Serial.print(myIMU.readFloatGyroX(), 3);
Serial.print(',');
Serial.print(myIMU.readFloatGyroY(), 3);
Serial.print(',');
Serial.print(myIMU.readFloatGyroZ(), 3);
Serial.println();

if (samplesRead == numSamples) {
// add an empty line if it's the last sample
Serial.println();
}
}
}
tip

由于 LSM6DS3 库的更新,如果您之前已经添加了此库,您需要重新下载 2.0.4 或更高版本,并将 ZIP 文件添加到 Arduino 中。

功能概述

  • 包含库文件

      #include <LSM6DS3.h> 
    #include <Wire.h>
    • 包含与LSM6DS3传感器通信的库。
    • 包含I2C通信的库。
  • 创建传感器实例

    • LSM6DS3 myIMU(I2C_MODE, 0x6A) 为IMU传感器创建LSM6DS3类的实例,指定I2C通信模式和设备地址0x6A。
  • 变量和常量

    • float aX, aY, aZ, gX, gY, gZ: 存储加速度计和陀螺仪数据的变量。
    • const float accelerationThreshold = 2.5: 检测显著运动的阈值,单位为G。
    • const int numSamples = 119: 检测到显著运动后要收集的样本数量。
    • int samplesRead = numSamples: 将样本计数器初始化为总样本数,表示尚未收集任何数据。
  • 基本设置

      pinMode(PD5,OUTPUT);
    digitalWrite(PD5,HIGH);
    • 打开陀螺仪使能引脚。
  • 数据处理

    aX = myIMU.readFloatAccelX();:
    aY = myIMU.readFloatAccelY();:
    aZ = myIMU.readFloatAccelZ();:
    float aSum = fabs(aX) + fabs(aY) + fabs(aZ);
    • 读取X轴加速度。
    • 读取Y轴加速度。
    • 读取Z轴加速度。
    • 计算加速度数据绝对值的总和,fabs()返回绝对值。
      // check if it's above the threshold
    if (aSum >= accelerationThreshold) {
    // reset the sample read count
    samplesRead = 0;
    break;
    }
    • 如果加速度绝对值的总和大于或等于设定的阈值,将样本计数samplesRead重置为0并退出循环。
  • 检查数据

    while (samplesRead < numSamples) {
    samplesRead++;
    .
    .
    .
    .
    .
    if (samplesRead == numSamples) {
    // add an empty line if it's the last sample
    Serial.println();
    }
    }
    • 进入另一个循环并检查是否已读取所需数量的样本。
    • 增加samplesRead的计数。
    • 如果已读取所有样本,打印一个空行来分隔数据输出。

结果图表

更多

如果您想要更多示例代码,请点击:"File" -> Example -> Seeed Arduino LSM6DS3"

IMU高级演示

硬件准备

Seeeduino-XIAO-Expansion-BoardSeeed Studio XIAO MG24 Sense

软件准备

tip

我们需要在工具栏中选择相应的堆栈来烧录程序。

程序代码
#include <LSM6DS3.h>
#include "Wire.h"

#define DEVICE_NAME "XIAO MG24 鼠标"

#define IMU_ACC_X_THRESHOLD 10
#define IMU_ACC_Y_THRESHOLD 10

// 鼠标按钮事件
#define LMB_PRESSED 1

// HID 报告数据
struct mouse_data {
int8_t delta_x;
int8_t delta_y;
uint8_t buttons;
};
static mouse_data report;

// HID 报告数据缓冲区
static uint8_t report_array[] = { 0x00, 0x00, 0x00 };

static uint8_t connection_handle = SL_BT_INVALID_CONNECTION_HANDLE;
static uint32_t bonding_handle = SL_BT_INVALID_BONDING_HANDLE;
static uint16_t hid_report;

// 设备信息服务
const uint8_t manufacturer[] = "Silicon Labs";
const uint8_t model_no[] = "1";
const uint8_t serial_no[] = "1";
const uint8_t hw_rev[] = "1";
const uint8_t fw_rev[] = "1";
const uint8_t sw_rev[] = "1";

static bd_addr ble_address = { 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u };
static bool button_press = false;
static bool button_press_prev = false;
static int32_t acc_x = 0, acc_y = 0;
static sl_status_t sc = SL_STATUS_OK;

static void ble_initialize_gatt_db();
static void ble_start_advertising();
static void mouse_button_callback();

// 可选择连接一个按钮用于左键点击
#define MOUSE_BUTTON D1

LSM6DS3 myIMU(I2C_MODE, 0x6A);

void setup()
{
// 初始化报告数据
memset(&report, 0, sizeof(report));

// 启用 IMU 电源
pinMode(PD5, OUTPUT);
digitalWrite(PD5, HIGH);
delay(300);

pinMode(MOUSE_BUTTON, INPUT_PULLUP);
attachInterrupt(MOUSE_BUTTON, mouse_button_callback, CHANGE);

Serial.begin(115200);

Serial.println("XIAO MG24 BLE 鼠标");

myIMU.begin();
Serial.println("---");
Serial.println("IMU 已初始化");
}

void loop()
{
// 更新'左鼠标按钮'位
if (button_press) {
report.buttons |= LMB_PRESSED;
if (!button_press_prev) {
button_press_prev = true;
Serial.println("按钮按下");
}
} else {
button_press_prev = false;
report.buttons &= ~LMB_PRESSED;
}

// 更改 x 和 y 以获得正确的板子方向
acc_y = (int32_t)(myIMU.readFloatAccelX() * 10.0f);
acc_x = (int32_t)(myIMU.readFloatAccelY() * 10.0f * -1.0f);

// 如果加速度值在正方向或负方向上超过阈值
// 则分配阈值
if (acc_x > IMU_ACC_X_THRESHOLD) {
report.delta_x = IMU_ACC_X_THRESHOLD;
} else if (acc_x < (-1 * IMU_ACC_X_THRESHOLD)) {
report.delta_x = (-1 * IMU_ACC_X_THRESHOLD);
} else {
report.delta_x = acc_x;
}

if (acc_y > IMU_ACC_Y_THRESHOLD) {
report.delta_y = IMU_ACC_Y_THRESHOLD;
} else if (acc_y < (-1 * IMU_ACC_Y_THRESHOLD)) {
report.delta_y = (-1 * IMU_ACC_Y_THRESHOLD);
} else {
report.delta_y = acc_y;
}

memcpy(report_array, &report, sizeof(report));
if (connection_handle != SL_BT_INVALID_CONNECTION_HANDLE && bonding_handle != SL_BT_INVALID_BONDING_HANDLE) {
// 通过 GATT 通知指示报告数据更改
sc = sl_bt_gatt_server_notify_all(hid_report, sizeof(report_array), report_array);
if (sc != SL_STATUS_OK) {
Serial.print("sl_bt_gatt_server_notify_all() 返回错误代码 0x");
Serial.println(sc, HEX);
} else {
Serial.print("光标 [delta-X: ");
Serial.print(report.delta_x, DEC);
Serial.print(" delta-Y: ");
Serial.print(report.delta_y, DEC);
Serial.print(" ] LMB: ");
Serial.println(report.buttons, HEX);
}
}
}

/******************************************************************************
* 鼠标按钮回调
*****************************************************************************/
void mouse_button_callback()
{
if (digitalRead(MOUSE_BUTTON) == LOW) {
button_press = true;
} else {
button_press = false;
}
}

/******************************************************************************
* 蓝牙协议栈事件处理程序
* 当 BLE 协议栈上发生事件时调用
*
* @param[in] evt 来自蓝牙协议栈的事件
*****************************************************************************/
void sl_bt_on_event(sl_bt_msg_t* evt)
{
sl_status_t sc = SL_STATUS_OK;
uint8_t ble_address_type;

switch (SL_BT_MSG_ID(evt->header)) {
// -------------------------------
// 此事件表示设备已启动且无线电已就绪
case sl_bt_evt_system_boot_id:
{
// 获取 BLE 地址和地址类型
sc = sl_bt_system_get_identity_address(&ble_address, &ble_address_type);
app_assert_status(sc);

// 打印欢迎消息
Serial.begin(115200);
Serial.println();
Serial.println("BLE 协议栈已启动");

// 初始化应用程序特定的 GATT DB
ble_initialize_gatt_db();

// HID 输入设备需要强制安全级别和绑定
sc = sl_bt_sm_configure(0, sl_bt_sm_io_capability_noinputnooutput);
app_assert_status(sc);

// 允许绑定
sc = sl_bt_sm_set_bondable_mode(1);
app_assert_status(sc);

ble_start_advertising();
}
break;

// -------------------------------
// 此事件表示 BLE 连接已打开
case sl_bt_evt_connection_opened_id:
{
// 存储连接句柄,发送指示时需要用到
connection_handle = evt->data.evt_connection_opened.connection;
bonding_handle = evt->data.evt_connection_opened.bonding;
Serial.print("连接已打开 - 句柄 0x");
Serial.println(connection_handle, HEX);

if (bonding_handle == SL_BT_INVALID_BONDING_HANDLE) {
Serial.println("连接尚未绑定");
} else {
Serial.println("连接已绑定");
}

Serial.println("提高安全性");
sc = sl_bt_sm_increase_security(evt->data.evt_connection_opened.connection);
app_assert_status(sc);
}
break;

// -------------------------------
// 此事件表示绑定成功
case sl_bt_evt_sm_bonded_id:
{
Serial.print("已绑定 - 句柄: 0x");
Serial.print(evt->data.evt_sm_bonded.connection, HEX);
bonding_handle = evt->data.evt_sm_bonded.bonding;
connection_handle = evt->data.evt_sm_bonded.connection;

Serial.print(" - 安全模式: 0x");
Serial.println(evt->data.evt_sm_bonded.security_mode, HEX);
}
break;

// -------------------------------
// 此事件表示BLE连接已关闭
case sl_bt_evt_connection_closed_id:
{
Serial.print("连接已关闭 - 句柄: 0x");
Serial.print(connection_handle, HEX);
Serial.print(" 原因: 0x");
Serial.println(evt->data.evt_connection_closed.reason, HEX);

connection_handle = SL_BT_INVALID_CONNECTION_HANDLE;
bonding_handle = SL_BT_INVALID_BONDING_HANDLE;

sc = sl_bt_sm_delete_bondings();
Serial.println("已删除绑定");
app_assert_status(sc);

ble_start_advertising();
}
break;

// -------------------------------
// 此事件表示连接参数已更改
case sl_bt_evt_connection_parameters_id:
{
Serial.print("设置连接参数,安全模式: ");
Serial.println(evt->data.evt_connection_parameters.security_mode, HEX);
}
break;

// -------------------------------
// 此事件表示绑定失败
case sl_bt_evt_sm_bonding_failed_id:
{
Serial.print("绑定失败,原因: 0x");
Serial.println(evt->data.evt_sm_bonding_failed.reason, HEX);
Serial.println("删除绑定。");

sc = sl_bt_sm_delete_bondings();
app_assert_status(sc);

Serial.println("绑定已删除");
Serial.print("关闭连接 - 句柄: 0x");
Serial.println(evt->data.evt_sm_bonding_failed.connection, HEX);
}
break;

// -------------------------------
// 默认事件处理程序
default:
break;
}
}

/******************************************************************************
* 启动BLE广播
* 如果是第一次调用,则初始化广播
*****************************************************************************/
static void ble_start_advertising()
{
static uint8_t advertising_set_handle = 0xff;
static bool init = true;
sl_status_t sc;

if (init) {
// 创建广播集
sc = sl_bt_advertiser_create_set(&advertising_set_handle);
app_assert_status(sc);

// 设置广播间隔为100ms
sc = sl_bt_advertiser_set_timing(
advertising_set_handle,
160, // 最小广播间隔(毫秒 * 1.6)
160, // 最大广播间隔(毫秒 * 1.6)
0, // 广播持续时间
0); // 最大广播事件数
app_assert_status(sc);

init = false;
}

// 生成广播数据
sc = sl_bt_legacy_advertiser_generate_data(advertising_set_handle, sl_bt_advertiser_general_discoverable);
app_assert_status(sc);

// 启动广播并启用连接
sc = sl_bt_legacy_advertiser_start(advertising_set_handle, sl_bt_advertiser_connectable_scannable);
app_assert_status(sc);

Serial.print("开始以 '");
Serial.print(DEVICE_NAME);
Serial.print("' 地址广播: ");
// 以 'FF:FF:FF:FF:FF:FF' 格式打印地址
for (uint8_t i = (sizeof(bd_addr) - 1); i > 0; i--) {
Serial.print(ble_address.addr[i], HEX);
Serial.print(":");
}
Serial.println(ble_address.addr[0], HEX);
}

/******************************************************************************
* 初始化GATT数据库
* 创建新的GATT会话并添加特定的服务和特征
*****************************************************************************/
static void ble_initialize_gatt_db()
{
sl_status_t sc;
uint16_t gattdb_session_id;
uint16_t service;
uint16_t characteristic;
uint16_t descriptor;

// 创建新的GATT数据库
sc = sl_bt_gattdb_new_session(&gattdb_session_id);
app_assert_status(sc);

// 通用访问服务
uint8_t generic_access_service_uuid[] = { 0x00, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
2,
generic_access_service_uuid,
&service);
app_assert_status(sc);

// 设备名称特征
sl_bt_uuid_16_t device_name_uuid = { .data = { 0x00, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
(SL_BT_GATTDB_CHARACTERISTIC_READ | SL_BT_GATTDB_CHARACTERISTIC_WRITE),
0,
0,
device_name_uuid,
sl_bt_gattdb_fixed_length_value,
strlen(DEVICE_NAME),
strlen(DEVICE_NAME),
(uint8_t *)DEVICE_NAME,
&characteristic);
app_assert_status(sc);

// 外观特征
sl_bt_uuid_16_t appearence_uuid = { .data = { 0x01, 0x2A } };
const uint8_t appearance_value[] = { 0xC2, 0x03 };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
appearence_uuid,
sl_bt_gattdb_fixed_length_value,
2,
sizeof(appearance_value),
appearance_value,
&characteristic);
app_assert_status(sc);

// 启动通用访问服务
sc = sl_bt_gattdb_start_service(gattdb_session_id, service);
app_assert_status(sc);

// 电池服务
const uint8_t battery_service_uuid[] = { 0x0F, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
0,
sizeof(battery_service_uuid),
battery_service_uuid,
&service);
app_assert_status(sc);

// 电池电量特征
const sl_bt_uuid_16_t battery_level_uuid = { .data = { 0x19, 0x2A } };
const uint8_t battery_level_init_value = 100;
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
battery_level_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(battery_level_init_value),
sizeof(battery_level_init_value),
&battery_level_init_value,
&characteristic);
app_assert_status(sc);

// 特征表示格式描述符
const sl_bt_uuid_16_t chara_presentation_format_descriptor_uuid = { .data = { 0x04, 0x29 } };
const uint8_t chara_presentation_format_value[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
sc = sl_bt_gattdb_add_uuid16_descriptor(gattdb_session_id,
characteristic,
SL_BT_GATTDB_DESCRIPTOR_READ,
0,
chara_presentation_format_descriptor_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(chara_presentation_format_value),
sizeof(chara_presentation_format_value),
chara_presentation_format_value,
&descriptor);
app_assert_status(sc);

// 客户端特征配置描述符
const sl_bt_uuid_16_t client_configuration_descriptor_uuid = { .data = { 0x02, 0x29 } };
const uint8_t client_configuration_value[] = { 0x00, 0x00 };
sc = sl_bt_gattdb_add_uuid16_descriptor(gattdb_session_id,
characteristic,
(SL_BT_GATTDB_DESCRIPTOR_READ | SL_BT_GATTDB_DESCRIPTOR_WRITE),
0,
client_configuration_descriptor_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(client_configuration_value),
sizeof(client_configuration_value),
client_configuration_value,
&descriptor);
app_assert_status(sc);

// 电池服务启动
sc = sl_bt_gattdb_start_service(gattdb_session_id, service);
app_assert_status(sc);

// 设备信息服务
const uint8_t device_info_service_uuid[] = { 0x0A, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
0,
sizeof(device_info_service_uuid),
device_info_service_uuid,
&service);
app_assert_status(sc);

// 制造商名称字符串特征
const sl_bt_uuid_16_t manufacturer_uuid = { .data = { 0x29, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
manufacturer_uuid,
sl_bt_gattdb_fixed_length_value,
(sizeof(manufacturer) - 1),
(sizeof(manufacturer) - 1),
manufacturer,
&characteristic);
app_assert_status(sc);

// 型号字符串特征
const sl_bt_uuid_16_t model_no_uuid = { .data = { 0x24, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
model_no_uuid,
sl_bt_gattdb_fixed_length_value,
(sizeof(model_no) - 1),
(sizeof(model_no) - 1),
model_no,
&characteristic);
app_assert_status(sc);

// 序列号字符串特征
const sl_bt_uuid_16_t serial_no_uuid = { .data = { 0x25, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
serial_no_uuid,
sl_bt_gattdb_fixed_length_value,
(sizeof(serial_no) - 1),
(sizeof(serial_no) - 1),
serial_no,
&characteristic);
app_assert_status(sc);

// 硬件版本字符串特征
const sl_bt_uuid_16_t hw_rev_uuid = { .data = { 0x27, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
hw_rev_uuid,
sl_bt_gattdb_fixed_length_value,
(sizeof(hw_rev) - 1),
(sizeof(hw_rev) - 1),
hw_rev,
&characteristic);
app_assert_status(sc);

// 固件版本字符串特征
const sl_bt_uuid_16_t fw_rev_uuid = { .data = { 0x26, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
fw_rev_uuid,
sl_bt_gattdb_fixed_length_value,
(sizeof(fw_rev) - 1),
(sizeof(fw_rev) - 1),
fw_rev,
&characteristic);
app_assert_status(sc);

// 软件版本字符串特征
const sl_bt_uuid_16_t sw_rev_uuid = { .data = { 0x28, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
sw_rev_uuid,
sl_bt_gattdb_fixed_length_value,
(sizeof(sw_rev) - 1),
(sizeof(sw_rev) - 1),
sw_rev,
&characteristic);
app_assert_status(sc);

// 系统ID特征
const sl_bt_uuid_16_t sys_id_uuid = { .data = { 0x23, 0x2A } };
const uint8_t sys_id_initial_value[] = { 0x12, 0x34, 0x56, 0xFF, 0xFE, 0x9A, 0xBC, 0xDE };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
sys_id_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(sys_id_initial_value),
sizeof(sys_id_initial_value),
sys_id_initial_value,
&characteristic);
app_assert_status(sc);

// PnP ID特征
const sl_bt_uuid_16_t pnp_id_uuid = { .data = { 0x50, 0x2A } };
const uint8_t pnp_id_initial_value[] = { 0x02, 0x10, 0xC4, 0x00, 0x01, 0x00, 0x01 };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
pnp_id_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(pnp_id_initial_value),
sizeof(pnp_id_initial_value),
pnp_id_initial_value,
&characteristic);
app_assert_status(sc);

// 设备信息服务启动
sc = sl_bt_gattdb_start_service(gattdb_session_id, service);
app_assert_status(sc);

// HID服务
uint8_t hid_service_uuid[] = { 0x12, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
2,
hid_service_uuid,
&service);
app_assert_status(sc);

// 协议模式特征
sl_bt_uuid_16_t hid_protocol_mode_uuid = { .data = { 0x4E, 0x2A } };
const uint8_t hid_protocol_mode_init_value[] = { 1 };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
(SL_BT_GATTDB_CHARACTERISTIC_READ | SL_BT_GATTDB_CHARACTERISTIC_WRITE_NO_RESPONSE),
0,
0,
hid_protocol_mode_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(hid_protocol_mode_init_value),
sizeof(hid_protocol_mode_init_value),
hid_protocol_mode_init_value,
&characteristic);
app_assert_status(sc);

// HID报告特征
const sl_bt_uuid_16_t hid_report_uuid = { .data = { 0x4D, 0x2A } };
const uint8_t hid_report_init_value[] = { 0x00, 0x00, 0x00 };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
(SL_BT_GATTDB_CHARACTERISTIC_READ | SL_BT_GATTDB_CHARACTERISTIC_WRITE | SL_BT_GATTDB_CHARACTERISTIC_NOTIFY),
0,
0,
hid_report_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(hid_report_init_value),
sizeof(hid_report_init_value),
hid_report_init_value,
&characteristic);
app_assert_status(sc);
hid_report = characteristic;

// HID报告引用描述符
const sl_bt_uuid_16_t hid_report_reference_desc_uuid = { .data = { 0x08, 0x29 } };
const uint8_t hid_report_reference_desc_init_val[] = { 0x00, 0x01 };
sc = sl_bt_gattdb_add_uuid16_descriptor(gattdb_session_id,
characteristic,
SL_BT_GATTDB_DESCRIPTOR_READ,
SL_BT_GATTDB_ENCRYPTED_READ,
hid_report_reference_desc_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(hid_report_reference_desc_init_val),
sizeof(hid_report_reference_desc_init_val),
hid_report_reference_desc_init_val,
&descriptor);
app_assert_status(sc);

// HID 报告映射特征
const sl_bt_uuid_16_t hid_report_map_uuid = { .data = { 0x4B, 0x2A } };
const uint8_t hid_report_map_init_value[] = { 0x05, 0x01, // 使用页面(通用桌面)
0x09, 0x02, // 使用(鼠标)
0xA1, 0x01, // 集合(应用程序)
0x09, 0x01, // 使用ID(指针)
0xA1, 0x00, // 集合(物理)
0x09, 0x30, // 使用ID(x)
0x09, 0x31, // 使用ID(y)
0x15, 0x80, // 逻辑最小值(-128)
0x25, 0x7F, // 逻辑最大值(127)
0x95, 0x02, // 报告计数(2)
0x75, 0x08, // 报告大小(8)
0x81, 0x06, // 输入(数据,变量,相对,无环绕,线性,首选状态,无空位置,位字段)
0x05, 0x09, // 使用页面(按钮)
0x19, 0x01, // 使用ID最小值(按钮1)
0x29, 0x03, // 使用ID最大值(按钮3)
0x15, 0x00, // 逻辑最小值(0)
0x25, 0x01, // 逻辑最大值(1)
0x95, 0x03, // 报告计数(3)
0x75, 0x01, // 报告大小(1)
0x81, 0x02, // 输入(数据,变量,绝对,无环绕,线性,首选状态,无空位置,位字段)
0x95, 0x01, // 报告计数(1)
0x75, 0x05, // 报告大小(5)
0x81, 0x03, // 输入(常量,变量,绝对,无环绕,线性,首选状态,无空位置,位字段)
0xC0, // 结束集合()
0xC0 }; // 结束集合()
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
SL_BT_GATTDB_ENCRYPTED_READ,
0,
hid_report_map_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(hid_report_map_init_value),
sizeof(hid_report_map_init_value),
hid_report_map_init_value,
&characteristic);
app_assert_status(sc);

// HID 外部报告引用描述符
const sl_bt_uuid_16_t hid_external_report_reference_descriptor_uuid = { .data = { 0x07, 0x29 } };
const uint8_t hid_external_report_reference_value[] = { 0x00, 0x00 };
sc = sl_bt_gattdb_add_uuid16_descriptor(gattdb_session_id,
characteristic,
SL_BT_GATTDB_DESCRIPTOR_READ,
0,
hid_external_report_reference_descriptor_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(hid_external_report_reference_value),
sizeof(hid_external_report_reference_value),
hid_external_report_reference_value,
&descriptor);
app_assert_status(sc);

// HID 信息特征
const sl_bt_uuid_16_t hid_info_uuid = { .data = { 0x4A, 0x2A } };
const uint8_t hid_info_init_value[] = { 0x01, 0x11, 0x00, 0x02 };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0,
0,
hid_info_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(hid_info_init_value),
sizeof(hid_info_init_value),
hid_info_init_value,
&characteristic);
app_assert_status(sc);

// HID 控制点特征
const sl_bt_uuid_16_t hid_control_point_uuid = { .data = { 0x4C, 0x2A } };
const uint8_t hid_control_point_init_value[] = { 0x00 };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service,
SL_BT_GATTDB_CHARACTERISTIC_WRITE_NO_RESPONSE,
0,
0,
hid_control_point_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(hid_control_point_init_value),
sizeof(hid_control_point_init_value),
hid_control_point_init_value,
&characteristic);
app_assert_status(sc);

// HID 服务启动
sc = sl_bt_gattdb_start_service(gattdb_session_id, service);
app_assert_status(sc);

// 提交 GATT DB 更改
sc = sl_bt_gattdb_commit(gattdb_session_id);
app_assert_status(sc);
}

#ifndef BLE_STACK_SILABS
#error "此示例仅与 Silicon Labs BLE 协议栈兼容。请在 'Tools > Protocol stack' 中选择 'BLE (Silabs)'。"
#endif

结果图表

当我们按下扩展板上的按钮时,我们可以观察到鼠标事件被触发了!

XIAO MG24 Sense 麦克风(Seeed Studio 演示)

内置传感器概述

麦克风传感器MSM381ACT001 是一款 MEMS(微机电系统)麦克风,设计用于以高灵敏度和低噪声捕获音频信号。具体来说,MSM381ACT001 具有以下特性:

麦克风功能:

  • 捕获声波并将其转换为电信号,能够在各种环境中检测音频输入。
  • 它具有宽频率响应范围,通常从 20 Hz 到 20 kHz,适用于各种音频应用,包括语音识别和音乐播放。

主要特性

  • 高灵敏度:能够检测微弱的声音,使其非常适合需要精确音频捕获的应用。
  • 低噪声:设计提供高信噪比(SNR),即使在嘈杂环境中也能确保清晰的音频输出。
  • 紧凑尺寸:MEMS 技术允许小型化设计,便于集成到智能手机和可穿戴设备等便携式设备中。
  • 数字输出:提供数字信号输出选项(例如 I2S),简化与数字信号处理器(DSP)和微控制器的接口。

软件准备

点击 github 下载链接来驱动麦克风传感器。

tip

目前我们需要手动修改替换文件,后续直接下载库即可使用,请等待我们的 wiki 更新。

更改文件路径

  • /Users/yourname/Library/Arduino15/packages/SiliconLabs/hardware/silabs/2.2.0/variants/xiao_mg24/ble_silabs/

代码实现

#include <mic.h>
#if defined(WIO_TERMINAL)
#include "processing/filters.h"
#endif

// 设置
#if defined(WIO_TERMINAL)
#define DEBUG 1 // 在 ISR 期间启用引脚脉冲
#define SAMPLES 16000*3
#elif defined(ARDUINO_ARCH_NRF52840)
#define DEBUG 1 // 在 ISR 期间启用引脚脉冲
#define SAMPLES 800
#elif defined(ARDUINO_SILABS)
#define DEBUG 1 // 在 ISR 期间启用引脚脉冲
#define SAMPLES 800
#endif

mic_config_t mic_config{
.channel_cnt = 1,
.sampling_rate = 16000,
.buf_size = 1600,
#if defined(WIO_TERMINAL)
.debug_pin = 1 // 切换每个 DAC ISR(如果 DEBUG 设置为 1)
#elif defined(ARDUINO_ARCH_NRF52840)
.debug_pin = LED_BUILTIN // 切换每个 DAC ISR(如果 DEBUG 设置为 1)
#elif defined(ARDUINO_SILABS)
.debug_pin = LED_BUILTIN // 切换每个 DAC ISR(如果 DEBUG 设置为 1)
#endif
};

#if defined(WIO_TERMINAL)
DMA_ADC_Class Mic(&mic_config);
#elif defined(ARDUINO_ARCH_NRF52840)
NRF52840_ADC_Class Mic(&mic_config);
#elif defined(ARDUINO_SILABS)
MG24_ADC_Class Mic(&mic_config);
#endif

int16_t recording_buf[SAMPLES];
volatile uint8_t recording = 0;
volatile static bool record_ready = false;

#if defined(WIO_TERMINAL)
FilterBuHp filter;
#endif

void setup() {

Serial.begin(115200);
while (!Serial) {delay(10);}

#if defined(WIO_TERMINAL)
pinMode(WIO_KEY_A, INPUT_PULLUP);
#endif

Mic.set_callback(audio_rec_callback);

if (!Mic.begin()) {
Serial.println("麦克风初始化失败");
while (1);
}

Serial.println("麦克风初始化完成。");

}

void loop() {

#if defined(WIO_TERMINAL)
if (digitalRead(WIO_KEY_A) == LOW && !recording) {

Serial.println("开始采样");
recording = 1;
record_ready = false;
}
#endif

#if defined(WIO_TERMINAL)
if (!recording && record_ready)
#elif defined(ARDUINO_ARCH_NRF52840) || defined(ARDUINO_SILABS)
if (record_ready)
#endif
{
Serial.println("采样完成");

for (int i = 0; i < SAMPLES; i++) {

//int16_t sample = filter.step(recording_buf[i]);
int16_t sample = recording_buf[i];
Serial.println(sample);
}

record_ready = false;
}
}

static void audio_rec_callback(uint16_t *buf, uint32_t buf_len) {

static uint32_t idx = 0;
// 将样本从 DMA 缓冲区复制到推理缓冲区
#if defined(WIO_TERMINAL)
if (recording)
#endif
{
for (uint32_t i = 0; i < buf_len; i++) {

// 将 12 位无符号 ADC 值转换为 16 位 PCM(有符号)音频值
#if defined(WIO_TERMINAL)
recording_buf[idx++] = filter.step((int16_t)(buf[i] - 1024) * 16);
//recording_buf[idx++] = (int16_t)(buf[i] - 1024) * 16;
#elif defined(ARDUINO_ARCH_NRF52840) || defined(ARDUINO_SILABS)
recording_buf[idx++] = buf[i];
#endif

if (idx >= SAMPLES){
idx = 0;
recording = 0;
record_ready = true;
break;
}
}
}

}

功能概述

麦克风配置

mic_config_t mic_config{
.channel_cnt = 1,
.sampling_rate = 16000,
.buf_size = 1600,
#if defined(WIO_TERMINAL)
.debug_pin = 1
#elif defined(ARDUINO_ARCH_NRF52840)
.debug_pin = LED_BUILTIN
#elif defined(ARDUINO_SILABS)
.debug_pin = LED_BUILTIN
#endif
};
  • mic_config_t: 定义麦克风配置结构体。
  • channel_cnt: 设置为 1 表示单声道。
  • sampling_rate: 设置为 16000 Hz 采样频率。
  • buf_size: 设置为 1600 缓冲区大小。
  • debug_pin: 根据平台设置调试引脚,用于调试期间的信号指示。

麦克风实例化

#if defined(WIO_TERMINAL)
DMA_ADC_Class Mic(&mic_config);
#elif defined(ARDUINO_ARCH_NRF52840)
NRF52840_ADC_Class Mic(&mic_config);
#elif defined(ARDUINO_SILABS)
MG24_ADC_Class Mic(&mic_config);
#endif
  • 条件编译: 为不同平台创建适当的麦克风类实例,使用之前定义的配置。

录音缓冲区和标志

int16_t recording_buf[SAMPLES];
volatile uint8_t recording = 0;
volatile static bool record_ready = false;
  • recording_buf: 定义一个 SAMPLES 大小的数组来存储录音样本。
  • recording: 一个 volatile 变量,标记当前是否正在录音,防止编译器优化。
  • record_ready: 一个 volatile 静态变量,指示录音是否完成并准备好进行进一步处理。

滤波器示例(适用于 WIO Terminal)

#if defined(WIO_TERMINAL)
FilterBuHp filter;
#endif
  • 如果在 WIO Terminal 上,创建一个高通滤波器实例用于滤波处理。

setup

void setup() {
Serial.begin(115200);
while (!Serial) {delay(10);}

#if defined(WIO_TERMINAL)
pinMode(WIO_KEY_A, INPUT_PULLUP);
#endif

Mic.set_callback(audio_rec_callback);

if (!Mic.begin()) {
Serial.println("Mic initialization failed");
while (1);
}

Serial.println("Mic initialization done.");
}
  • 初始化串口: 以 115200 波特率启动串口通信并等待串口准备就绪。
  • 设置引脚模式: 在 WIO Terminal 上,将按键引脚设置为输入上拉模式。
  • 设置回调函数: 调用 Mic.set_callback(audio_rec_callback) 指定录音时的回调函数。
  • 初始化麦克风: 调用 Mic.begin(),如果初始化失败,打印错误信息并进入死循环。

loop

void loop() { 
#if defined(WIO_TERMINAL)
if (digitalRead(WIO_KEY_A) == LOW && !recording) {
Serial.println("Starting sampling");
recording = 1;
record_ready = false;
}
#endif

#if defined(WIO_TERMINAL)
if (!recording && record_ready)
#elif defined(ARDUINO_ARCH_NRF52840) || defined(ARDUINO_SILABS)
if (record_ready)
#endif
{
Serial.println("Finished sampling");

for (int i = 0; i < SAMPLES; i++) {
int16_t sample = recording_buf[i];
Serial.println(sample);
}

record_ready = false;
}
}
  • 检测按键: 在 WIO Terminal 上,当检测到按键被按下且未在录音时开始录音。
  • 完成采样: 如果未在录音且 record_ready 设置为 true,则打印"Finished sampling"。
  • 遍历录音缓冲区并打印每个样本值。

音频录音回调函数

static void audio_rec_callback(uint16_t *buf, uint32_t buf_len) {
static uint32_t idx = 0;
#if defined(WIO_TERMINAL)
if (recording)
#endif
{
for (uint32_t i = 0; i < buf_len; i++) {
#if defined(WIO_TERMINAL)
recording_buf[idx++] = filter.step((int16_t)(buf[i] - 1024) * 16);
#elif defined(ARDUINO_ARCH_NRF52840) || defined(ARDUINO_SILABS)
recording_buf[idx++] = buf[i];
#endif

if (idx >= SAMPLES){
idx = 0;
recording = 0;
record_ready = true;
break;
}
}
}
}
  • 回调函数: 在音频录音期间调用,负责将样本从 DMA 缓冲区复制到录音缓冲区。
  • 条件编译: 如果在 WIO Terminal 上,使用滤波器处理输入。
  • 将 12 位无符号 ADC 值转换为 16 位 PCM(有符号)音频值。
  • 样本填充: 将样本复制到 recording_buf 并更新索引 idx。
  • 完成录音: 如果填充的样本数达到 SAMPLES,重置索引,标记录音结束并设置 record_ready 为 true。

结果图表

这是识别声音的波形,当你吹气时,可以清楚地看到波形振荡幅度变大。

更多

如果你想要更多示例代码,请点击:-> "Example -> Seeed Arduino Mic"

XIAO MG24 Sense 麦克风(Silicon Labs 演示)

tip

我们需要下载最新的板载包(2.3.0)才能在示例中找到最新的官方代码

软件准备

代码实现

/*
模拟麦克风音量示例

该示例展示了模拟MEMS麦克风的使用方法,并根据麦克风的输入音量调节
板载LED的亮度。
此示例与所有Silicon Labs Arduino开发板兼容,但需要
板载模拟麦克风或连接到指定引脚的模拟麦克风。

作者:Áron Gyapjas (Silicon Labs)
*/

#include <SilabsMicrophoneAnalog.h>

// 此配置适用于Seeed Studio XIAO MG24上的MSM381ACT001麦克风
// 请根据您的硬件进行更改
#define MIC_DATA_PIN PC9
#define MIC_PWR_PIN PC8
#define NUM_SAMPLES 128
#define MIC_VALUE_MIN 735
#define MIC_VALUE_MAX 900

// 用于存储采样的缓冲区
uint32_t mic_buffer[NUM_SAMPLES];
uint32_t mic_buffer_local[NUM_SAMPLES];

volatile bool data_ready_flag = false;
MicrophoneAnalog micAnalog(MIC_DATA_PIN, MIC_PWR_PIN);
void mic_samples_ready_cb();
void calculate_and_display_voice_level();

void setup()
{
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);

micAnalog.begin(mic_buffer, NUM_SAMPLES);
Serial.println("麦克风已初始化...");

micAnalog.startSampling(mic_samples_ready_cb);
Serial.println("采样已开始...");
}

void loop()
{
if (data_ready_flag) {
data_ready_flag = false;
calculate_and_display_voice_level();
}
}

// 当从麦克风获得请求数量的采样时调用
void mic_samples_ready_cb()
{
// 将数据复制到本地缓冲区以防止被覆盖
memcpy(mic_buffer_local, mic_buffer, NUM_SAMPLES * sizeof(uint32_t));
data_ready_flag = true;
}

void calculate_and_display_voice_level() {
// 用于平滑语音电平的滚动平均值
static uint32_t rolling_average = 0u;

// 停止采样以防止覆盖当前数据
micAnalog.stopSampling();

// 获取采样值的平均值
uint32_t voice_level = (uint32_t)micAnalog.getAverage(mic_buffer_local, NUM_SAMPLES);
// 根据麦克风输出的最小值/最大值调整语音电平
voice_level = constrain(voice_level, MIC_VALUE_MIN, MIC_VALUE_MAX);
// 计算滚动平均值
rolling_average = (voice_level + rolling_average) / 2;

// 将当前平均电平映射到亮度
int brightness = map(rolling_average, MIC_VALUE_MIN, MIC_VALUE_MAX, 0, 255);
if (LED_BUILTIN_ACTIVE == LOW) {
analogWrite(LED_BUILTIN, 255 - brightness);
} else {
analogWrite(LED_BUILTIN, brightness);
}
// 打印平均语音电平(您可以使用串口绘图器在图表上查看此值)
Serial.println(rolling_average);

// 重新开始采样
micAnalog.startSampling(mic_samples_ready_cb);
}

功能概述

头文件介绍

#include <SilabsMicrophoneAnalog.h>
  • 包含 SilabsMicrophoneAnalog.h 头文件,该文件包含使用模拟麦克风所需的库函数和定义。

硬件配置

#define MIC_DATA_PIN  PC9
#define MIC_PWR_PIN PC8
#define NUM_SAMPLES 128
#define MIC_VALUE_MIN 735
#define MIC_VALUE_MAX 900
  • MIC_DATA_PIN: 将麦克风数据引脚定义为 PC9

  • MIC_PWR_PIN: 将麦克风电源引脚定义为 PC8

  • NUM_SAMPLES: 将每次采样的样本数定义为 128。

  • MIC_VALUE_MINMIC_VALUE_MAX: 定义麦克风输出的最小值和最大值范围。

缓冲区定义

uint32_t mic_buffer[NUM_SAMPLES];
uint32_t mic_buffer_local[NUM_SAMPLES];
  • mic_buffer: 用于存储从麦克风收集的原始采样数据。

  • mic_buffer_local: 用于临时存储采样数据以防止覆盖。

标志和对象定义

volatile bool data_ready_flag = false;
MicrophoneAnalog micAnalog(MIC_DATA_PIN, MIC_PWR_PIN);
  • data_ready_flag: 一个标志,用于指示新的采样数据是否准备就绪。

  • micAnalog: 创建一个 MicrophoneAnalog 对象来控制麦克风。

回调函数声明

void mic_samples_ready_cb();
void calculate_and_display_voice_level();
  • mic_samples_ready_cb(): 采样完成时调用的回调函数。

  • calculate_and_display_voice_level(): 计算音量并控制LED亮度的函数。

setup() 函数

void setup()
{
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);

micAnalog.begin(mic_buffer, NUM_SAMPLES);
Serial.println("Microphone initialized...");

micAnalog.startSampling(mic_samples_ready_cb);
Serial.println("Sampling started...");
}
  • 以115200的波特率初始化串口通信。

  • 将板载LED引脚设置为输出模式。

  • 初始化麦克风并指定采样缓冲区。

  • 开始采样并设置采样完成时的回调函数。

loop()函数

void loop()
{
if (data_ready_flag) {
data_ready_flag = false;
calculate_and_display_voice_level();
}
}
  • 检查 data_ready_flag 是否为 true,表示新数据已准备就绪。

  • 如果有新数据可用,调用 calculate_and_display_voice_level() 函数来处理数据。

void mic_samples_ready_cb()
{
memcpy(mic_buffer_local, mic_buffer, NUM_SAMPLES * sizeof(uint32_t));
data_ready_flag = true;
}

将采样数据从 mic_buffer 复制到 mic_buffer_local 以防止覆盖。

data_ready_flag 设置为 true 以指示新数据已准备就绪。


void calculate_and_display_voice_level() {
static uint32_t rolling_average = 0u;

micAnalog.stopSampling();

uint32_t voice_level = (uint32_t)micAnalog.getAverage(mic_buffer_local, NUM_SAMPLES);
voice_level = constrain(voice_level, MIC_VALUE_MIN, MIC_VALUE_MAX);
rolling_average = (voice_level + rolling_average) / 2;

int brightness = map(rolling_average, MIC_VALUE_MIN, MIC_VALUE_MAX, 0, 255);
if (LED_BUILTIN_ACTIVE == LOW) {
analogWrite(LED_BUILTIN, 255 - brightness);
} else {
analogWrite(LED_BUILTIN, brightness);
}
Serial.println(rolling_average);

micAnalog.startSampling(mic_samples_ready_cb);
}
  • 停止采样以防止数据覆盖。

  • 计算采样数据的平均值并将其限制在 MIC_VALUE_MINMIC_VALUE_MAX 之间。

  • 计算滚动平均值以平滑音量变化。

  • 将滚动平均值映射到 LED 亮度范围(0 到 255)并调整 LED 亮度。

  • 通过串口输出滚动平均值以观察音量变化。

  • 重新启动采样以收集新的音频数据。

结果图表

当我们对着麦克风吹气时,可以看到顶部的 LED 会随着声音变亮和变暗。

更多示例

如果您想要更多示例代码,请点击:-> "Example -> SilabsMicrophoneAnalog -> MicrophoneVolume"

资源

适用于 Seeed Studio XIAO MG24 Sense

技术支持与产品讨论

感谢您选择我们的产品!我们在这里为您提供不同的支持,以确保您使用我们产品的体验尽可能顺畅。我们提供多种沟通渠道,以满足不同的偏好和需求。

Loading Comments...