基于XG24-EK2703A的BLE HID蓝牙键盘+鼠标复合设备功能开发(BLE+HID+FreeRTOS+Gecko SDK)

本文介绍了如何利用SiliconLabsXG24-EK2703A开发板通过HID协议创建蓝牙键盘+鼠标复合设备,通过按键控制翻页和发送字符,展示了使用SimplicityStudio5IDE进行开发、配置和功能实现的过程。

👉 【Funpack3-1】基于XG24-EK2703A的BLE HID蓝牙键盘+鼠标复合设备
👉 Github: EmbeddedCamerata/XG24_ble_hid_keymouse

项目介绍

本项目基于 Silicon Labs XG24-EK2703A 开发板,通过 HID 协议实现了一个蓝牙键盘+鼠标复合设备,可通过按键实现上下翻页、发送字符功能。使用板载两个按键,当 BTN0 按下,向上翻页;当BTN1按下,向下翻页;当两按键同时按下2s后,向主机依次发送字符“EETREE.CN”。

👉 Simplicity Studio 5

硬件介绍

XG24-EK2703A是一款基于EFR32MG24片上系统的开发套件,具备超低成本、低功耗和小巧的特点。该套件支持2.4GHz无线通信,兼容蓝牙LE、蓝牙mesh、Zigbee、Thread和Matter协议,为无线物联网产品的开发和原型制作提供了极大的便利。包含:

  1. 一个USB接口
  2. 一个板载SEGGER J-Link 调试器,支持SWD
  3. 两个LED和两个按钮
  4. 虚拟COM端口
  5. 数据包跟踪接口(PTI)
  6. 一个支持外部硬件连接的mikroBus插座和一个Qwiic连接器
  7. 32 位 ARM Cortex-M33,78 MHz最高工作频率
  8. 1536 kB 闪存和 256 kB RAM

XG24板卡资源图

项目设计

开发环境及工程参考

本项目使用Silicon Labs官方的IDE Simplicity Studio 5开发,使用Gecko SDK v4.4.0,GNU ARM Toolchain 12.2。工程目录上,按照Bluetooth - SoC Empty 空白示例的代码组织形式即可。主要的业务代码写在 app.capp.h 内,外设、驱动及蓝牙部分通过 .slcp 文件配置。

👉 本工程参考SiliconLabs蓝牙应用示例:bluetooth_hid_keyboard

总体流程图

所使用的系统外设:两个按键、两个LED及蓝牙栈。

  • 在按键中断回调中,根据不同按键按下,置位或清除各按键按下的事件
  • 使用FreeRTOS操作系统,创建按键响应任务,用以实现两个按键按下的响应服务:循环读取按键按下事件,当按键单独按下时,则用一枚举变量 km_status 记录:
    • 当BTN0按下,置 KM_SCROLL_UP
    • 当BTN1按下,置 KM_SCROLL_DOWN
    • 当同时按下,且无定时器在运行,则开启2s定时器,该定时器绑定一回调函数,在该回调内:置 km_statusKM_SEND_STRING,同时反转两LED状态(便于观察现象)
    • 最后,都向蓝牙栈发送外部事件信号
  • 在蓝牙事件回调中,当接收到外部事件信号后,根据 km_status 值进行相应操作。从而实现上/下翻页、发送字符的功能。

系统工作流程图

硬件基本配置

在基于 “Bluetooth - Soc Empty” 空白示例的基础上,打开 .slcp 文件,在 SOFTWARE COMPONENTS 选项卡下安装如下组件:

  • [Platform] → [Driver] → [Button] → [Simple Button],例化 btn0 与 btn1,对应开发板上两个按键,均设置为中断模式
  • [Platform] → [Driver] → [LED] → [Simple LED],例化 led0 与 led1,对应开发板上两个 LED
  • [Services] → [IO Stream] → [IO Stream: USART],保持默认配置即可
  • [Application] → [Utility] → [Timer for FreeRTOS]
  • [Application] → [Utility] → [Log]

并且,参考SiliconLabs蓝牙应用示例:bluetooth_hid_keyboard,使用该示例提供的 GATT 配置,导入到自己的工程中:

  1. 打开项目中 .slcp 文件
  2. 在 CONFIGURATION TOOLS 选项卡下找到 Bluetooth GATT Configurator
  3. 导入 config/btconf/gatt_configuration.btconf 文件
  4. 保存 GATT 配置

后续还会进行一定程度的修改。

应用初始化

app.h 内,定义四种按键按下的枚举类型,分别表示:未按下、发送字符(两按键同时按下)、上翻页(BTN0按下)及下翻页(BTN1按下):

typedef enum
{
   
   
  KM_IDLE = 0U,
  KM_SEND_STRING = 1U,
  KM_SCROLL_UP = 2U,
  KM_SCROLL_DOWN = 3U,
} km_status_t;

在初始化阶段,先创建按键按下事件组、按键响应任务。

#define KM_BTN_TASK_NAME        "keymouse_btn"
#define KM_BTN_TASK_STACK_SIZE  1024
#define KM_BTN_TASK_STATIC      0

TaskHandle_t km_btn_task_handle = NULL;
static EventGroupHandle_t xbtn_events = NULL;
static km_status_t km_status = KM_IDLE;

SL_WEAK void app_init(void)
{
   
   
  	xbtn_events = xEventGroupCreate();
  	if (xbtn_events == NULL) {
   
   
  		app_log_error("BTN events create failed\r\n");
	}
  	xTaskCreate(km_btn_task,
              	KM_BTN_TASK_NAME,
             	configMINIMAL_STACK_SIZE,
              	NULL,
              	tskIDLE_PRIORITY,
              	&km_btn_task_handle);
}

按键中断回调

按键中断回调定义在 void sl_button_on_change(const sl_button_t *handle) 内,可参考示例修改。在此,根据触发中断的句柄判断是哪个按键按下或释放,相应地置位或清除事件位 xbtn_events

#include "sl_simple_button_instances.h"
#define BTN0_PRESSED            (1 << 0)
#define BTN1_PRESSED            (1 << 1)
#define BTN_NONE_PRESSED        0
#define BTN_BOTH_PRESSED        (BTN0_PRESSED | BTN1_PRESSED)
void sl_button_on_change(const sl_button_t *handle)
{
   
   
	BaseType_t xHigherPriorityTaskWoken;
  	if (&sl_but
#include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h> #include <esp_bt.h> #include <esp_bt_main.h> #include <esp_wifi.h> #include <esp_sleep.h> #include <DHT.h> #include <WiFi.h> #include <Preferences.h> #include <HardwareSerial.h> #include <LITTLEFS.h> #include <Audio.h> #include <freertos/FreeRTOS.h> #include <freertos/task.h> #include <HTTPClient.h> #include <ArduinoJson.h> // #include <BLE2902.h> // 电机控制引脚定义 #define MOTOR_A_IN1 4 #define MOTOR_A_IN2 5 #define MOTOR_B_IN1 6 #define MOTOR_B_IN2 7 #define LED_1 15 #define LED_2 37 #define A_EN 35 #define B_EN 36 #define DHTPIN 11 // GPIO15连接DHT11数据线 #define DHTTYPE DHT11 // 传感器类型 DHT dht(DHTPIN, DHTTYPE); // I2S引脚定义 #define I2S_BCLK 20 #define I2S_LRC 21 #define I2S_DIN 19 unsigned long lastSendTime = 0; const long sendInterval = 1000; // 发送间隔1秒 // BLE服务UUID定义 #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" #define T_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a9"//温度 #define RH_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a7"//湿度 #define DS_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a6"//距离 #define wifeid_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a5"//wife名 #define pwd_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a4"//wife密码 #define Mp3_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a0"//播放指定音乐 // 全局对象与状态 Audio audio; bool isPlaying = false; // 全局变量增加播放标志位 bool startPlayFlag = false; // 触发播放的标志位 String currentSong = ""; // 存储当前要播放的歌曲路径 String playUrl=""; volatile bool isFetchingUrl = false; // 酷我接口配置(与之前一致) const char* kuwoSearchApi = "http://search.kuwo.cn/r.s?all="; const char* kuwoPlayApi = "https://antiserver.kuwo.cn/anti.s?type=convert_url&rid=MUSIC_"; // #define SERVICE_UUID "00001800-0000-1000-8000-00805F9B34FB" // #define CHARACTERISTIC_UUID "00001801-0000-1000-8000-00805F9B34FB" BLEServer *pServer = nullptr; BLEService *pService = nullptr; BLECharacteristic *pCharacteristic = nullptr;//发送蓝牙数据 BLECharacteristic *pChar1; BLECharacteristic *pChar2; BLECharacteristic *pChar3; BLECharacteristic *pChar4; BLECharacteristic *pChar5; BLECharacteristic *pChar0;//播放指定音乐 // BLECharacteristic* pChar2; // BLECharacteristic* pChar3; // BLECharacteristic *pChar1 = nullptr;//发送传感器数据 // BLECharacteristic *pChar2 = nullptr;//发送传感器数据 // BLECharacteristic *pChar3 = nullptr;//发送传感器数据 bool deviceConnected = false; bool oldDeviceConnected = false; bool dataReceived=false; uint32_t counter = 0; // 示例数据(计数器) unsigned long previousMillis = 0; const long interval = 1000; // 发送间隔1秒 const int trigPin = 9; // GPIO5 const int echoPin = 10; // GPIO18 Preferences preferences; bool wifiConnected = false; String ssid = ""; String password = ""; // 低功耗模式设置 #define BLE_CONNECTION_TIMEOUT 30000 // 30秒无连接进入睡眠 uint32_t lastConnectionTime = 0; HardwareSerial ASRSerial(2); // 使用UART2(GPIO16-RX, GPIO17-TX) // 电机控制函数 //前进 void moveForward() { digitalWrite(MOTOR_A_IN1, LOW); digitalWrite(MOTOR_A_IN2, HIGH); digitalWrite(MOTOR_B_IN1, LOW); digitalWrite(MOTOR_B_IN2, HIGH); Serial.println("F"); } //后退 void moveBackward() { digitalWrite(MOTOR_A_IN1, HIGH); digitalWrite(MOTOR_A_IN2, LOW); digitalWrite(MOTOR_B_IN1, HIGH); digitalWrite(MOTOR_B_IN2, LOW); Serial.println("B"); } void turnLeft() { digitalWrite(MOTOR_A_IN1, LOW); digitalWrite(MOTOR_A_IN2, HIGH); digitalWrite(MOTOR_B_IN1, HIGH); digitalWrite(MOTOR_B_IN2, LOW); Serial.println("L"); } void turnRight() { digitalWrite(MOTOR_A_IN1, HIGH); digitalWrite(MOTOR_A_IN2, LOW); digitalWrite(MOTOR_B_IN1, LOW); digitalWrite(MOTOR_B_IN2, HIGH); Serial.println("R"); } void stopMotors() { digitalWrite(MOTOR_A_IN1, LOW); digitalWrite(MOTOR_A_IN2, LOW); digitalWrite(MOTOR_B_IN1, LOW); digitalWrite(MOTOR_B_IN2, LOW); Serial.println("S"); } void setSpeed(int speed) { // 如果使用PWM控制速度,可以在这里实现 analogWrite(A_EN, speed); analogWrite(B_EN, speed); } void offled(){ digitalWrite(LED_1, LOW); digitalWrite(LED_2, LOW); } void onled(){ digitalWrite(LED_1, HIGH); digitalWrite(LED_2, HIGH); } float getDistance(){ // 发送10μs触发脉冲 // delay(500); digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // 测量高电平持续时间 long duration = pulseIn(echoPin, HIGH); // 计算距离(单位:厘米) float distance = duration * 0.034 / 2; return distance; // Serial.print("距离: "); // Serial.print(distance); // Serial.println(" cm"); // 每0.5秒测量一次 } void sendData(){ // float distance=getDistance(); // float humidity = dht.readHumidity(); // float tempC = dht.readTemperature(); // if (deviceConnected && (millis() - lastSendTime >= sendInterval)) { lastSendTime = millis(); // 更新时间戳 // 模拟三组字符串数据 // float Data[3] = {1,2,3}; // byte dataBytes[12]; // memcpy(dataBytes, &Data, 12); // // 发送数据 // pCharacteristic->setValue(dataBytes, 12); // 假设获取温度 // float temperature = 12; // uint8_t bytes[4]; // memcpy(bytes, &temperature, 4); // 将浮点数转为字节数组 // String value1="123"; // String l="|"; // String value2="234"; // float a=0; // float b=10; // float e=100; // float c=0; // String d="|"; // String data = a+d+b+d+e+d+c+d; float Th=10; float DH=20; float DS=30; String T_s=String(Th,1); String DH_s=String(DH,1); String DS_s=String(DS,1); pChar1->setValue(T_s.c_str()); pChar1->notify(); pChar2->setValue(DH_s.c_str()); pChar2->notify(); pChar3->setValue(DS_s.c_str()); pChar3->notify(); } } void connectToWiFi() { // esp_bluedroid_disable(); // 先关闭BLE // // 禁用Wi-Fi睡眠 // WiFi.setSleep(false); // 关闭睡眠模式 // // WiFi.setChannel(11); // 使用信道6(中心频率2437MHz) WiFi.begin(ssid.c_str(), password.c_str()); // WiFi.begin("honor","wz111111"); Serial.println("Connecting to WiFi..."); int retries = 0; while (WiFi.status() != WL_CONNECTED && retries < 10) { delay(1000); Serial.print("."); retries++; } if (WiFi.status() == WL_CONNECTED) { Serial.println("\nWiFi Connected! IP: " + WiFi.localIP().toString()); WiFi.setSleep(false); // 禁用WiFi睡眠 //关闭蓝牙以节能(可选) // BLEDevice::deinit(); // delay(1000); // String data="连接成功"; // pChar6->setValue(data.c_str()); // pChar6->notify(); // // 设置特征值 wifiConnected = true; } else { Serial.println("\nWiFi Connection Failed. Entering Bluetooth Mode."); // pCharacteristic->setValue("连接失败!"); // pCharacteristic->notify(); Serial.println(ssid.c_str()); Serial.println(password.c_str()); Serial.println(WiFi.status()); // setupBluetooth(); } } class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; lastConnectionTime = millis(); Serial.println("Device connected"); // 连接时提高性能 setCpuFrequencyMhz(80); }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; Serial.println("Device disconnected"); // 断开连接后重新开始广播 pServer->getAdvertising()->start(); // 降低功耗 setCpuFrequencyMhz(40); } }; // 从不同格式的播放指令中提取歌曲名 String extractSongName(String command) { // 定义可能的指令前缀(按优先级排序) String prefixes[] = { "播放一首", "播放一曲", "播放", "放一首", "放一曲", "放" }; int prefixCount = 6; // 前缀数量 // 预处理:去除命令中的空格和常见标点 command.replace(" ", ""); command.replace("。", ""); command.replace(",", ""); command.replace("!", ""); command.replace("?", ""); command.replace("?", ""); // 遍历所有前缀,查找匹配项 for (int i = 0; i < prefixCount; i++) { int index = command.indexOf(prefixes[i]); if (index == 0) { // 前缀必须在命令开头 // 提取前缀后面的部分作为歌曲名 String songName = command.substring(prefixes[i].length()); // 检查是否提取到有效内容 if (songName.length() > 0) { return songName; } } } // 未找到匹配的指令格式 return ""; } class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { String value = pCharacteristic->getValue(); if (value.length()>0) { if (pCharacteristic->getUUID().toString() == wifeid_UUID ) { ssid = value.c_str(); } else if (pCharacteristic->getUUID().toString() == pwd_UUID) { password = value.c_str(); // 收到密码后,保存并连接Wi-Fi preferences.putString("ssid", ssid); preferences.putString("password", password); connectToWiFi(); }else if (pCharacteristic->getUUID().toString() ==Mp3_UUID) { String Name=extractSongName(value.c_str()); // currentSong = "/002.wav"; // // currentSong=Name; // startPlayFlag = true; // 触发播放 String ID=getSongId(Name); Serial.println(ID); playUrl=getPlayUrl(ID); Serial.println("收到播放链接"+playUrl); if (ID.isEmpty()) { Serial.println("未找到歌曲ID"); return; } playMusic(playUrl); // 创建音频任务(优化堆栈和优先级) Serial.println("收到播放指令:" + Name); } // String data = String(value.c_str()); // int colonIndex = data.indexOf(':'); // if (colonIndex != -1) { // wifiSSID = data.substring(0, colonIndex); // wifiPassword = data.substring(colonIndex + 1); // dataReceived = true; // 标记数据已接收 // Serial.println("Received Wi-Fi credentials: SSID=" + wifiSSID + ", Password=" + wifiPassword); // } // Serial.print("Received Value: "); // Serial.println(value.c_str()); // 更新最后活动时间 lastConnectionTime = millis(); switch(value[0]) { case 'A': moveForward(); break; case 'B': moveBackward(); break; case 'L': turnLeft(); break; case 'R': turnRight(); break; case 'S': stopMotors(); break; case '0': offled(); break; // 关灯 case '1': onled(); break; // 开灯 case '2': setSpeed(128); break; // 中速 case '3': setSpeed(255); break; // 高速 default: break; } } } }; void initSystem() { // 初始化串口 // Serial.begin(115200); // 初始化LittleFS if (!LITTLEFS.begin(true)) { Serial.println("LittleFS初始化失败"); while (1); // 初始化失败则阻塞 } Serial.println("LittleFS初始化成功"); // 配置音频输出 audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DIN); // audio.setCaching(16384); audio.setVolume(10); // 降低音量减少底噪和电源负担 } // 播放音频文件 void playAudio(const char* path) { if (isPlaying) { audio.stopSong(); vTaskDelay(50 / portTICK_PERIOD_MS); // 等待停止完成 } // 检查文件存在性 if (!LITTLEFS.exists(path)) { Serial.printf("文件 %s 不存在\n", path); return; } // 尝试播放 if (audio.connecttoFS(LITTLEFS, path)) { Serial.printf("开始播放: %s\n", path); isPlaying = true; } else { Serial.printf("播放失败: %s\n", path); isPlaying = false; } } // 音频处理任务 void audioTask(void *param) { // 任务启动延迟,确保系统初始化完成 vTaskDelay(200 / portTICK_PERIOD_MS); Serial.println("音频任务就绪"); while (true) { if (startPlayFlag) { startPlayFlag = false; // 重置标志位 playAudio(currentSong.c_str()); // 播放指定歌曲 } audio.loop(); if (isPlaying) { audio.loop(); // 播放结束检测 if (!audio.isRunning()) { isPlaying = false; audio.stopSong(); // 确保资源释放 Serial.println("播放结束"); } } vTaskDelay(1 / portTICK_PERIOD_MS); // 低延迟调度 } } void initBlue(){ // 初始化BLE BLEDevice::init("ESP32_car1"); BLEDevice::setPower(ESP_PWR_LVL_N12); // 设置低功率传输 // 创建BLE服务器 pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // 创建BLE服务 pService = pServer->createService(SERVICE_UUID); // 创建特征值 pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pChar0 = pService->createCharacteristic( Mp3_UUID , BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pChar1 = pService->createCharacteristic( T_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pChar2 = pService->createCharacteristic( RH_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pChar3 = pService->createCharacteristic( DS_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pChar4 = pService->createCharacteristic( wifeid_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pChar5 = pService->createCharacteristic( pwd_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pCharacteristic->setCallbacks(new MyCallbacks()); pChar0->setCallbacks(new MyCallbacks()); pChar1->setCallbacks(new MyCallbacks()); pChar2->setCallbacks(new MyCallbacks()); pChar3->setCallbacks(new MyCallbacks()); pChar4->setCallbacks(new MyCallbacks()); pChar5->setCallbacks(new MyCallbacks()); // pChar1->setCallbacks(new MyCallbacks()); // pChar2->setCallbacks(new MyCallbacks()); // pChar3->setCallbacks(new MyCallbacks()); // pCharacteristic->setValue("Hello World"); // 启动服务 pService->start(); // 开始广播 BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); // 有助于iPhone连接 pAdvertising->setMinPreferred(0x12); BLEDevice::startAdvertising(); Serial.println("Waiting for a client connection..."); } // 步骤1:获取歌曲ID(与之前一致) String getSongId(String songName) { Serial.println("搜索歌曲:" + songName); String searchUrl = String(kuwoSearchApi) + songName + "&ft=music&itemset=web_2013&client=kt&pn=0&rn=1&rformat=json&encoding=utf8"; isFetchingUrl = true; HTTPClient http; http.begin(searchUrl); int httpCode = http.GET(); String songId = ""; if (httpCode == HTTP_CODE_OK) { String response = http.getString(); DynamicJsonDocument doc(4096); DeserializationError error = deserializeJson(doc, response); if (error) { Serial.println("JSON解析失败:" + String(error.c_str())); http.end(); return ""; } String musicRid = doc["abslist"][0]["MUSICRID"].as<String>(); if (musicRid.startsWith("MUSIC_")) { songId = musicRid.substring(6); Serial.println("歌曲ID:" + songId); } } else { Serial.printf("搜索失败,错误码:%d\n", httpCode); } http.end(); isFetchingUrl = false; return songId; } // 步骤2:获取播放链接(与之前一致) // String getPlayUrl(String songId) { // Serial.println("获取播放链接,ID:" + songId); // String Id="205994482"; // String playApiUrl = String(kuwoPlayApi) + Id + "&format=mp3&response=url"; // if (WiFi.status() != WL_CONNECTED) { // Serial.println("❌ WiFi未连接,无法获取播放链接"); // return ""; // } // WiFiClientSecure client; // client.setInsecure(); // 忽略 HTTPS 证书验证(旧版本核心支持此方法) // // 手动测试连接服务器(关键:直接连接域名和端口) // if (!client.connect("antiserver.kuwo.cn", 443)) { // 443是HTTPS默认端口 // Serial.println("❌ 无法连接到服务器(antiserver.kuwo.cn:443),可能被封锁"); // return ""; // } // HTTPClient http; // client.setTimeout(5000); // http.begin(client,playApiUrl); // // 使用 WiFiClientSecure 并忽略证书验证 // // http.setInsecure(); // 关键:忽略HTTPS证书验证(解决握手失败问题) // http.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); // http.addHeader("Referer", "https://www.kuwo.cn/"); // int httpCode = http.GET(); // String playUrl = ""; // if (httpCode == HTTP_CODE_OK) { // String response = http.getString(); // DynamicJsonDocument doc(1024); // DeserializationError error = deserializeJson(doc, response); // if (!error) { // playUrl = doc["url"].as<String>(); // Serial.println("播放链接:" + playUrl); // } // } else { // Serial.printf("播放链接获取失败,错误码:%d\n", httpCode); // } // http.end(); // return playUrl; // } /** * 通过MAX98357播放网络音乐 * @param url 音乐播放链接(MP3格式) */ String getPlayUrl(String songId) { // 注释硬编码ID,使用传入的songId(确保与搜索结果一致) // String Id="205994482"; isFetchingUrl = true; String playApiUrl = String(kuwoPlayApi) +songId+ "&br=64&format=mp3&response=url"; // 使用传入的ID if (WiFi.status() != WL_CONNECTED) { Serial.println("❌ WiFi未连接,无法获取播放链接"); return ""; } Serial.println("请求播放链接的URL:" + playApiUrl); // 打印完整URL,方便手动测试 WiFiClientSecure client; client.setInsecure(); // 忽略证书验证 client.setTimeout(5000); // 测试服务器连接 if (!client.connect("antiserver.kuwo.cn", 443)) { Serial.println("❌ 无法连接到服务器(antiserver.kuwo.cn:443)"); return ""; } Serial.println("✅ 服务器连接成功"); HTTPClient http; if (!http.begin(client, playApiUrl)) { // 检查HTTP初始化是否成功 Serial.println("❌ HTTP初始化失败"); client.stop(); return ""; } // 添加必要请求头 http.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); http.addHeader("Referer", "https://www.kuwo.cn/"); int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { playUrl = http.getString(); Serial.println("服务器原始响应:" + playUrl); // 打印完整响应,关键调试! // // 解析JSON // DynamicJsonDocument doc(1024); // DeserializationError error = deserializeJson(doc, response); // if (error) { // Serial.println("❌ JSON解析失败:" + String(error.c_str())); // http.end(); // client.stop(); // return ""; // } // // 检查是否存在"url"字段 // if (doc.containsKey("url")) { // playUrl = doc["url"].as<String>(); // Serial.println("✅ 解析到播放链接:" + playUrl); // } else { // Serial.println("❌ 响应中无\"url\"字段"); // } } else { Serial.printf("❌ 播放链接请求失败,错误码:%d\n", httpCode); } http.end(); client.stop(); isFetchingUrl = false; return playUrl; } // ==================== 音频播放任务 ==================== void playMusic(String url) { // 停止当前播放(若有) if (isPlaying) { audio.stopSong(); isPlaying = false; delay(100); // 等待资源释放 } Serial.printf("\n▶️ 开始连接音频:%s\n", playUrl.c_str()); //连接音频源(核心:初始化播放) if (!audio.connecttohost(url.c_str())) { Serial.println("❌ playMusic:无法连接到音频源"); return; } Serial.println("❌ playMusic:成功连接到音频源"); // // 等待连接成功(最多5秒) // unsigned long timeout = millis(); // while (!audio.isRunning() && millis() - timeout < 5000) { // delay(50); // audio.loop(); // 临时处理初始化逻辑 // } // 检查连接结果 if (audio.isRunning()) { Serial.println("✅ playMusic:音频连接成功,开始播放"); isPlaying = true; // 设置标志位,通知loop任务启动 } else { Serial.println("❌ playMusic:连接超时,未启动播放"); } } // void audioTask2(void* parameter) { // Serial.println("✅ 音频处理任务启动"); // unsigned long lastStatusTime = 0; // while (1) { // 任务内循环,替代原loop() // // 处理音频解码和输出(必须定期调用) // audio.loop(); // // // 打印播放状态 // // if (millis() - lastStatusTime > 500) { // // printAudioStatus(); // // lastStatusTime = millis(); // // } // // 检查播放是否结束 // if (!audio.isRunning()) { // Serial.println("\n📻 播放结束,任务退出"); // vTaskDelete(NULL); // 播放结束后删除任务 // } // vTaskDelay(10 / portTICK_PERIOD_MS); // 短暂延迟,降低CPU占用 // } // } // ==================== 音频循环任务(处理audio.loop())==================== void setup() { Serial.begin(115200); ASRSerial.begin(115200, SERIAL_8N1, 16, 17); // 初始化串口 Serial.println("Starting BLE car server..."); preferences.begin("wifi-creds", false); // 初始化存储 // 检查存储中是否有凭证 ssid = preferences.getString("ssid", ""); password = preferences.getString("password", ""); if(ssid != "" && password != ""){ connectToWiFi(); // 自动重连Wi-Fi Serial.println("wifi信息非空"); }else{ Serial.println("wifi信息为空"); } // } else { // setupBluetooth(); // 无凭证,启动蓝牙配置 // } dht.begin(); WiFi.mode(WIFI_STA); // 初始化电机控制引脚 pinMode(MOTOR_A_IN1, OUTPUT); pinMode(MOTOR_A_IN2, OUTPUT); pinMode(MOTOR_B_IN1, OUTPUT); pinMode(MOTOR_B_IN2, OUTPUT); pinMode(A_EN, OUTPUT); pinMode(B_EN, OUTPUT); pinMode(LED_1, OUTPUT); pinMode(LED_2, OUTPUT); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); digitalWrite(LED_1, LOW);//默认关灯 digitalWrite(LED_2, LOW);//默认关灯 stopMotors(); // 初始设置为低功耗模式 // setCpuFrequencyMhz(40); // 关闭WiFi以节省功耗 // WiFi.mode(WIFI_OFF); // btStop(); // esp_bt_controller_disable(); initBlue(); initSystem(); // 创建音频任务(优化堆栈和优先级) // xTaskCreate( // audioTask, // "AudioTask", // 16384, // 20KB堆栈(足够且不浪费) // NULL, // 3, // 中等优先级,避免抢占系统资源 // NULL // ); // xTaskCreate( // audioLoopTask, // "AudioTask", // 16380, // 20KB堆栈(足够且不浪费) // NULL, // 3, // 中等优先级,避免抢占系统资源 // NULL // ); } void loop() { if(isPlaying){ audio.loop(); // 检查播放状态 if (!audio.isRunning()) { Serial.println("📻 播放结束"); isPlaying = false; } } if(wifiConnected&&deviceConnected){ //if(deviceConnected){ delay(1000); String data="成功连接"+ssid; pCharacteristic->setValue(data.c_str()); pCharacteristic->notify(); Serial.println("success1"); wifiConnected=false; } // if(ASRSerial.available()) { // String response = ASRSerial.readStringUntil('\n'); // response.trim(); // // Serial.println(response); // switch(response [0]) { // case 'A': moveForward(); break; // case 'B': moveBackward(); break; // case 'L': turnLeft(); break; // case 'R': turnRight(); break; // case 'S': stopMotors(); break; // case '0': offled(); break; // 关灯 // case '1': onled(); break; // 开灯 // // case '2': setSpeed(128); break; // 中速 // // case '3': setSpeed(255); break; // 高速 // // case 'N': offled(); break; // 关灯 // // case '1': onled(); break; // 开灯 // default: break; // } // } }这代码导致音频播放断断续续同时有时候单片机重启
08-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值