CF827B:High Load(树 & 构造)

本文探讨了如何构造一个包含特定数量节点的网络,使得度为1的节点间的最大距离最小化。通过采用特定的网络构建策略,可以有效地降低这些关键节点之间的最远距离。

B. High Load
time limit per test
2 seconds
memory limit per test
512 megabytes
input
standard input
output
standard output

Arkady needs your help again! This time he decided to build his own high-speed Internet exchange point. It should consist of n nodes connected with minimum possible number of wires into one network (a wire directly connects two nodes). Exactly k of the nodes should be exit-nodes, that means that each of them should be connected to exactly one other node of the network, while all other nodes should be connected to at least two nodes in order to increase the system stability.

Arkady wants to make the system as fast as possible, so he wants to minimize the maximum distance between two exit-nodes. The distance between two nodes is the number of wires a package needs to go through between those two nodes.

Help Arkady to find such a way to build the network that the distance between the two most distant exit-nodes is as small as possible.

Input

The first line contains two integers n and k (3 ≤ n ≤ 2·1052 ≤ k ≤ n - 1) — the total number of nodes and the number of exit-nodes.

Note that it is always possible to build at least one network with n nodes and k exit-nodes within the given constraints.

Output

In the first line print the minimum possible distance between the two most distant exit-nodes. In each of the next n - 1 lines print two integers: the ids of the nodes connected by a wire. The description of each wire should be printed exactly once. You can print wires and wires' ends in arbitrary order. The nodes should be numbered from 1 to n. Exit-nodes can have any ids.

If there are multiple answers, print any of them.

Examples
input
3 2
output
2
1 2
2 3
input
5 3
output
3
1 2
2 3
3 4
3 5
Note

In the first example the only network is shown on the left picture.

In the second example one of optimal networks is shown on the right picture.

Exit-nodes are highlighted.


题意:给N个节点,要求构造出一棵树,度为1的点有K个,且这些点的最大距离最小。

思路:以1为圆心,向外扩展K条枝即可。

# include <bits/stdc++.h>
using namespace std;
int main()
{
    int n, k, pre=1;
    scanf("%d%d",&n,&k);
    int c = (int)(ceil((n-k-1)*1.0/k));
    c = c*2+1+((n-k-1)%k>1||(n-k-1)%k==0);
    printf("%d\n",c);
    for(int i=1; i<n; ++i)
    {
        if(i<=k) printf("%d %d\n",pre, i+1);
        else printf("%d %d\n",i+1-k, i+1);
    }
    return 0;
}



主机: #include &quot;BLE_master.h&quot; #include &lt;Arduino.h&gt; // ==================== 静态成员定义 ==================== const char* BLEMaster::DEVICE_NAME = &quot;GMD1032-BLE-Slave&quot;; const char* BLEMaster::TARGET_SERVICE_UUID = &quot;4fafc201-1fb5-459e-8fcc-c5c9c331914b&quot;; const char* BLEMaster::TARGET_CHAR_UUID = &quot;beb5483e-36e1-4688-b7f5-ea07361b26a8&quot;; bool BLEMaster::connected = false; NimBLEClient* BLEMaster::pClient = nullptr; BLERemoteCharacteristic* BLEMaster::pRemoteCharacteristic = nullptr; NimBLEAdvertisedDevice* BLEMaster::targetDevice = nullptr; void (*BLEMaster::dataCallback)(const char*) = nullptr; bool BLEMaster::shouldConnect = false; bool BLEMaster::isConnecting = false; // ==================== 开始扫描 ==================== void BLEMaster::begin() { Serial.println(&quot;🔄 Initializing BLE Master (NimBLE)...&quot;); NimBLEDevice::init(&quot;&quot;); NimBLEDevice::deleteAllBonds(); NimBLEScan* pScan = NimBLEDevice::getScan(); pScan-&gt;setScanCallbacks(new ScanCallback(), true); pScan-&gt;setActiveScan(true); pScan-&gt;setInterval(134.5f); pScan-&gt;setWindow(44.8f); Serial.println(&quot;🔍 Starting continuous BLE scan...&quot;); pScan-&gt;start(0, true); // 连续扫描 } // ==================== 设置数据回调 ==================== void BLEMaster::onDataReceived(void (*callback)(const char*)) { dataCallback = callback; } // ==================== 主循环:状态检查与连接控制 ==================== void BLEMaster::loop() { if (connected &amp;&amp; pClient &amp;&amp; pClient-&gt;isConnected()) { return; // 已连接,无需处理 } if (isConnecting) { return; // 正在连接,防止重复尝试 } NimBLEScan* pScan = NimBLEDevice::getScan(); // 如果扫描结束且标记了连接请求,则发起连接 if (shouldConnect &amp;&amp; targetDevice != nullptr &amp;&amp; !pScan-&gt;isScanning()) { isConnecting = true; connectToDevice(); shouldConnect = false; return; } // 如果长时间未连接,重启扫描 static unsigned long lastScanStart = 0; if (!pScan-&gt;isScanning() &amp;&amp; !connected &amp;&amp; millis() - lastScanStart &gt; 5000) { Serial.println(&quot;🔁 Restarting BLE scan...&quot;); pScan-&gt;start(30, false); lastScanStart = millis(); } } // ==================== 尝试连接目标设备 ==================== bool BLEMaster::connectToDevice() { if (!targetDevice) { Serial.println(&quot;❌ No target device to connect.&quot;); isConnecting = false; return false; } Serial.printf(&quot;👉 Connecting to: %s [MAC: %s]\n&quot;, targetDevice-&gt;getName().c_str(), targetDevice-&gt;getAddress().toString().c_str()); pClient = NimBLEDevice::createClient(); pClient-&gt;setClientCallbacks(new ClientCallback()); // 快速连接参数 (minInterval=6, maxInterval=12, timeout=100ms) pClient-&gt;setConnectionParams(0x06, 0x0C, 0, 0x64); // connect() 参数说明: // advertiseDevice: 扫描到的设备指针 // deleteAttrs: true 表示每次重新发现服务 // isDeleteCall: false // exchangeMTU: true &rarr; 自动协商大包传输 if (!pClient-&gt;connect(targetDevice, true, false, true)) { Serial.println(&quot;❌ Connection failed!&quot;); cleanupOnFailure(); return false; } Serial.println(&quot;✅ Connected! Discovering service...&quot;); BLERemoteService* pRemoteService = pClient-&gt;getService(NimBLEUUID(TARGET_SERVICE_UUID)); if (!pRemoteService) { Serial.println(&quot;❌ Service not found&quot;); cleanupOnFailure(); return false; } pRemoteCharacteristic = pRemoteService-&gt;getCharacteristic(NimBLEUUID(TARGET_CHAR_UUID)); if (!pRemoteCharacteristic) { Serial.println(&quot;❌ Characteristic not found&quot;); cleanupOnFailure(); return false; } requestMaxMTU(); // 启用通知 if (pRemoteCharacteristic-&gt;canNotify()) { bool subscribed = pRemoteCharacteristic-&gt;subscribe(true, [](BLERemoteCharacteristic* pBLERC, uint8_t* pData, size_t length, bool isNotify) { char jsonStr[512] = {0}; size_t copyLen = min(length, sizeof(jsonStr) - 1); memcpy(jsonStr, pData, copyLen); jsonStr[copyLen] = &#39;\0&#39;; if (dataCallback) { dataCallback((const char*)jsonStr); } else { Serial.printf(&quot;[BLE] Received (%u bytes): %.*s\n&quot;, length, copyLen, jsonStr); } }); if (subscribed) { Serial.println(&quot;🔔 Notify enabled.&quot;); } else { Serial.println(&quot;⚠️ Subscribe failed!&quot;); } } else { Serial.println(&quot;❌ Characteristic does not support notification.&quot;); } connected = true; isConnecting = false; return true; } // ==================== 清理失败状态 ==================== void BLEMaster::cleanupOnFailure() { if (pClient) { pClient-&gt;disconnect(); pClient = nullptr; } connected = false; pRemoteCharacteristic = nullptr; isConnecting = false; } // ==================== 请求最大 MTU ==================== void BLEMaster::requestMaxMTU() { if (pClient &amp;&amp; pClient-&gt;isConnected()) { uint16_t mtu = pClient-&gt;getMTU(); Serial.printf(&quot;📈 Current MTU: %u\n&quot;, mtu); if (!pClient-&gt;exchangeMTU()) { Serial.println(&quot;⚠️ MTU exchange failed!&quot;); } else { Serial.println(&quot;📤 MTU request sent (negotiating up to 512)&quot;); } } } // ==================== 广播设备回调 ==================== void BLEMaster::ScanCallback::onResult(const NimBLEAdvertisedDevice* advertisedDevice) { Serial.printf(&quot;🔍 Scanned: %s [%s], RSSI=%d\n&quot;, advertisedDevice-&gt;getName().c_str(), advertisedDevice-&gt;getAddress().toString().c_str(), advertisedDevice-&gt;getRSSI()); if (advertisedDevice-&gt;isAdvertisingService(NimBLEUUID(BLEMaster::TARGET_SERVICE_UUID))) { Serial.printf(&quot;✅ Target device found via UUID: %s [RSSI=%d]\n&quot;, advertisedDevice-&gt;getName().c_str(), advertisedDevice-&gt;getRSSI()); if (BLEMaster::targetDevice) { delete BLEMaster::targetDevice; } BLEMaster::targetDevice = new NimBLEAdvertisedDevice(*advertisedDevice); BLEMaster::shouldConnect = true; NimBLEDevice::getScan()-&gt;stop(); // 停止扫描 } } // ✅ 正确实现 onScanEnd:两个参数,const 引用 void BLEMaster::ScanCallback::onScanEnd(const NimBLEScanResults&amp; scanResults, int reason) { Serial.printf(&quot;🏁 Scan ended. Found %d devices. Reason code: %d\n&quot;, scanResults.getCount(), reason); // 可在此添加逻辑,例如重新开始扫描 // NimBLEDevice::getScan()-&gt;start(30, false); } // ==================== 客户端连接回调 ==================== void BLEMaster::ClientCallback::onConnect(NimBLEClient* pClient) { Serial.println(&quot;✔ BLE Device connected.&quot;); } void BLEMaster::ClientCallback::onDisconnect(NimBLEClient* pClient, int reason) { char addr[64]; snprintf(addr, sizeof(addr), &quot;%s&quot;, pClient-&gt;getPeerAddress().toString().c_str()); Serial.printf(&quot;🔌 Disconnected from %s, reason=%d\n&quot;, addr, reason); BLEMaster::connected = false; BLEMaster::pRemoteCharacteristic = nullptr; BLEMaster::pClient = nullptr; // 可在此添加延时后自动重连逻辑 } void BLEMaster::ClientCallback::onMTUChange(NimBLEClient* pClient, uint16_t MTU) { Serial.printf(&quot;📊 MTU changed to: %u\n&quot;, MTU); } 从机: #include &lt;SPI.h&gt; #include &lt;NimBLEDevice.h&gt; // 替换 BLEDevice.h #include &lt;NimBLE2904.h&gt; // 替换 BLE2902.h #include &lt;Arduino.h&gt; // ==================== 引脚定义 ==================== #define SCK_PIN 13 #define MISO_PIN 11 #define MOSI_PIN 14 #define CS_PIN 12 // ==================== 启用调试模式 ==================== #define DEBUG_GMD1032 // ==================== GMD1032 命令列表 ==================== const uint16_t CMD_START_MEASUREMENT = 0x2A16; const uint16_t CMD_CLEAR_ADC_RESULTS = 0x243C; const uint16_t CMD_READ_CFG0 = 0xC08E; const uint16_t CMD_READ_STS0 = 0xC9B1; // CELL 分组命令 const uint16_t CMD_READ_CELL_1_3 = 0xD0FE; const uint16_t CMD_READ_CELL_4_6 = 0xD1F9; const uint16_t CMD_READ_CELL_7_9 = 0xD2F0; const uint16_t CMD_READ_CELL_10_12 = 0xD3F7; const uint16_t CMD_READ_CELL_13_15 = 0xD4E2; const uint16_t CMD_READ_CELL_16_18 = 0xD5E5; // DIS 分组命令 const uint16_t CMD_READ_DIS_1_3 = 0xD6EC; const uint16_t CMD_READ_DIS_4_6 = 0xD7EB; const uint16_t CMD_READ_DIS_7_9 = 0xD8C6; const uint16_t CMD_READ_DIS_10_12 = 0xD9C1; const uint16_t CMD_READ_DIS_13_15 = 0xDAC8; const uint16_t CMD_READ_DIS_16_18 = 0xDBCF; // 其他测量命令 const uint16_t CMD_READ_VREF2_V3 = 0xDFD3; const uint16_t CMD_READ_V5_DIETEMP_VSTK = 0xE06E; // ==================== SPI 设置 ==================== SPISettings spiSettings(1000000, MSBFIRST, SPI_MODE3); // 1MHz, Mode3 (CPOL=1, CPHA=1) // ==================== BLE 配置 ==================== #define SERVICE_UUID &quot;4fafc201-1fb5-459e-8fcc-c5c9c331914b&quot; #define CHARACTERISTIC_UUID &quot;beb5483e-36e1-4688-b7f5-ea07361b26a8&quot; NimBLECharacteristic* pCharacteristic = nullptr; // 改为 NimBLE 类型 bool deviceConnected = false; // 共享数据缓冲区 float sharedCellVoltages[16] = {0}; float sharedDisVoltages[16] = {0}; float sharedVref2 = 0.0f, sharedV3 = 0.0f, sharedV5 = 0.0f, sharedDieTemp = 0.0f, sharedVstk = 0.0f; portMUX_TYPE sensorMutex = portMUX_INITIALIZER_UNLOCKED; // 保护共享数据 // ==================== 回调类声明 ==================== class MyServerCallbacks : public NimBLEServerCallbacks { // 正确签名:使用 NimBLEConnInfo&amp; void onConnect(NimBLEServer* pServer, NimBLEConnInfo&amp; connInfo) override { deviceConnected = true; Serial.println(&quot;设备接入~&quot;); } // 正确签名:包含 connInfo 和 reason void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo&amp; connInfo, int reason) override { deviceConnected = false; Serial.print(&quot;设备断开~ 原因代码: &quot;); Serial.println(reason); pServer-&gt;startAdvertising(); // 重新开始广播 } }; class MyCharacteristicCallbacks : public NimBLECharacteristicCallbacks { // 推荐加 const,并保留 override void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo&amp; connInfo) override { std::string value = pCharacteristic-&gt;getValue(); if (!value.empty()) { Serial.printf(&quot;📥 客户端 %s 写入数据: %s\n&quot;, std::string(connInfo.getAddress()).c_str(), // 正确调用 value.c_str()); } } }; // ==================== 辅助函数:打印二进制 ==================== void printBinary(uint8_t b) { for (int i = 7; i &gt;= 0; i--) { Serial.write((b &amp; (1 &lt;&lt; i)) ? &#39;1&#39; : &#39;0&#39;); } } // ==================== 唤醒芯片 ==================== void wakeUpGMD1032() { SPI.beginTransaction(spiSettings); digitalWrite(CS_PIN, LOW); delayMicroseconds(10); SPI.transfer(0x00); digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); delay(5); } // ==================== 发送命令 ==================== bool sendCommand(uint16_t command) { SPI.beginTransaction(spiSettings); digitalWrite(CS_PIN, LOW); SPI.transfer16(command); digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); return true; } // ==================== 解析逆序字段 ==================== void parseReverseFields(uint8_t* buffer, uint16_t* results, int count) { for (int i = 0; i &lt; count; i++) { int bufIdx = i * 2; results[count - 1 - i] = (buffer[bufIdx] &lt;&lt; 8) | buffer[bufIdx + 1]; } } // ==================== 读取所有 CELL 电压 ==================== bool readAllCellVoltages(float* cellVoltages) { const uint16_t cmds[] = { CMD_READ_CELL_1_3, CMD_READ_CELL_4_6, CMD_READ_CELL_7_9, CMD_READ_CELL_10_12, CMD_READ_CELL_13_15, CMD_READ_CELL_16_18 }; const int groupCount = 6; const int perGroup[] = {3, 3, 3, 3, 3, 3}; SPI.beginTransaction(spiSettings); for (int g = 0; g &lt; groupCount; g++) { digitalWrite(CS_PIN, LOW); SPI.transfer16(cmds[g]); uint8_t buffer[8]; for (int i = 0; i &lt; 8; i++) { buffer[i] = SPI.transfer(0x00); } digitalWrite(CS_PIN, HIGH); uint16_t raw[3]; parseReverseFields(buffer, raw, perGroup[g]); for (int i = 0; i &lt; perGroup[g]; i++) { int idx = g * 3 + i; if (idx &gt;= 16) continue; float voltage = (raw[i] - 10240) * 0.0001f; cellVoltages[idx] = voltage; } } SPI.endTransaction(); return true; } // ==================== 读取所有 DIS 电压 ==================== bool readAllDisVoltages(float* disVoltages) { const uint16_t cmds[] = { CMD_READ_DIS_1_3, CMD_READ_DIS_4_6, CMD_READ_DIS_7_9, CMD_READ_DIS_10_12, CMD_READ_DIS_13_15, CMD_READ_DIS_16_18 }; const int groupCount = 6; const int perGroup[] = {3, 3, 3, 3, 3, 3}; SPI.beginTransaction(spiSettings); for (int g = 0; g &lt; groupCount; g++) { digitalWrite(CS_PIN, LOW); SPI.transfer16(cmds[g]); uint8_t buffer[8]; for (int i = 0; i &lt; 8; i++) { buffer[i] = SPI.transfer(0x00); } digitalWrite(CS_PIN, HIGH); uint16_t raw[3]; parseReverseFields(buffer, raw, perGroup[g]); for (int i = 0; i &lt; perGroup[g]; i++) { int idx = g * 3 + i; if (idx &gt;= 16) continue; float voltage = (raw[i] - 10240) * 0.0001f; disVoltages[idx] = voltage; } } SPI.endTransaction(); return true; } // ==================== 读取 VREF2 和 V3 ==================== bool readVref2AndV3(float* vref2, float* v3) { uint8_t buffer[8]; SPI.beginTransaction(spiSettings); digitalWrite(CS_PIN, LOW); SPI.transfer16(CMD_READ_VREF2_V3); for (int i = 0; i &lt; 8; i++) { buffer[i] = SPI.transfer(0x00); } digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); uint16_t raw_v3 = (buffer[0] &lt;&lt; 8) | buffer[1]; uint16_t raw_vref2 = (buffer[2] &lt;&lt; 8) | buffer[3]; *v3 = (raw_v3 - 10240) * 0.0001f; *vref2 = (raw_vref2 - 10240) * 0.0001f; return true; } // ==================== 读取 V5 / DieTemp / VSTK ==================== bool readV5DieTempVStk(float* v5, float* dieTemp, float* vstk) { uint8_t buffer[8]; SPI.beginTransaction(spiSettings); digitalWrite(CS_PIN, LOW); SPI.transfer16(CMD_READ_V5_DIETEMP_VSTK); for (int i = 0; i &lt; 8; i++) { buffer[i] = SPI.transfer(0x00); } digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); uint16_t raw_vstk = (buffer[0] &lt;&lt; 8) | buffer[1]; uint16_t raw_dieTemp = (buffer[2] &lt;&lt; 8) | buffer[3]; uint16_t raw_v5 = (buffer[4] &lt;&lt; 8) | buffer[5]; *vstk = (raw_vstk - 10240) * 0.0016f; *v5 = (raw_v5 - 10240) * 0.0001f; if (raw_dieTemp &gt;= 8000 &amp;&amp; raw_dieTemp &lt;= 30000) { *dieTemp = raw_dieTemp * 0.27662 - 3105.59; } else { *dieTemp = NAN; } return true; } // ==================== 更新 BLE 特征值(从 Core 1 调用)==================== void updateBLECharacteristic() { static char jsonBuffer[512]; int offset = 0; float cell[16]; float dieTemp; // 安全拷贝共享数据 portENTER_CRITICAL(&amp;sensorMutex); memcpy(cell, sharedCellVoltages, sizeof(cell)); dieTemp = sharedDieTemp; portEXIT_CRITICAL(&amp;sensorMutex); // 构造 JSON: {&quot;cell&quot;:[...], &quot;temp&quot;: ...} offset += snprintf(jsonBuffer + offset, sizeof(jsonBuffer) - offset, &quot;{\&quot;cell\&quot;:[&quot;); for (int i = 0; i &lt; 16; i++) { offset += snprintf(jsonBuffer + offset, sizeof(jsonBuffer) - offset, &quot;%.3f%s&quot;, cell[i], (i &lt; 15) ? &quot;,&quot; : &quot;&quot;); } if (!isnan(dieTemp)) { offset += snprintf(jsonBuffer + offset, sizeof(jsonBuffer) - offset, &quot;],\&quot;temp\&quot;:%.2f}&quot;, dieTemp); } else { offset += snprintf(jsonBuffer + offset, sizeof(jsonBuffer) - offset, &quot;],\&quot;temp\&quot;:null}&quot;); } // 防溢出兜底 if (offset &gt;= sizeof(jsonBuffer)) { strcpy(jsonBuffer, &quot;{\&quot;err\&quot;:\&quot;long\&quot;}&quot;); } // 发送到 BLE if (pCharacteristic &amp;&amp; deviceConnected) { pCharacteristic-&gt;setValue(jsonBuffer); pCharacteristic-&gt;notify(); // 触发通知 } #ifdef DEBUG_GMD1032 Serial.printf(&quot;BLE Data: %s\n&quot;, jsonBuffer); #endif } // ==================== 核心 0:传感器采集任务 ==================== void sensorTask(void *parameter) { for (;;) { yield(); wakeUpGMD1032(); delay(2); sendCommand(CMD_START_MEASUREMENT); delay(10); float cellVoltages[16] = {0}; float disVoltages[16] = {0}; float vref2 = 0.0f, v3 = 0.0f, v5 = 0.0f, dieTemp = 0.0f, vstk = 0.0f; readAllCellVoltages(cellVoltages); readAllDisVoltages(disVoltages); readVref2AndV3(&amp;vref2, &amp;v3); readV5DieTempVStk(&amp;v5, &amp;dieTemp, &amp;vstk); // 写入共享变量(加锁) portENTER_CRITICAL(&amp;sensorMutex); memcpy(sharedCellVoltages, cellVoltages, sizeof(cellVoltages)); memcpy(sharedDisVoltages, disVoltages, sizeof(disVoltages)); sharedVref2 = vref2; sharedV3 = v3; sharedV5 = v5; sharedDieTemp = dieTemp; sharedVstk = vstk; portEXIT_CRITICAL(&amp;sensorMutex); #ifdef DEBUG_GMD1032 Serial.println(&quot;============== GMD1032 全通道数据 ==============&quot;); for (int i = 0; i &lt; 16; i++) { if (i % 4 == 0) { Serial.print(&quot; &quot;); } Serial.printf(&quot;CELL%2d:%5.3fV &quot;, i + 1, cellVoltages[i]); if ((i + 1) % 4 == 0 || i == 15) { Serial.println(); } } if (!isnan(dieTemp)) { Serial.printf(&quot;芯片温度: %.2f&deg;C\n&quot;, dieTemp); } else { Serial.println(&quot;芯片温度: 无效数据&quot;); } Serial.println(&quot;---------------------------------------------&quot;); #endif delay(1000); // 每秒采集一次 } } // ==================== 核心 1:BLE 服务任务 ==================== void bleTask(void *parameter) { NimBLEDevice::init(&quot;GMD1032-BLE-Slave&quot;); NimBLEServer* pServer = NimBLEDevice::createServer(); pServer-&gt;setCallbacks(new MyServerCallbacks()); NimBLEService* pService = pServer-&gt;createService(SERVICE_UUID); pCharacteristic = pService-&gt;createCharacteristic( CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY ); pCharacteristic-&gt;addDescriptor(new NimBLE2904()); pCharacteristic-&gt;setCallbacks(new MyCharacteristicCallbacks()); pCharacteristic-&gt;setValue(&quot;GMD1032 Ready&quot;); pService-&gt;start(); NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); // 显式设置广告内容 NimBLEAdvertisementData advertisementData; advertisementData.setName(&quot;GMD1032-BLE-Slave&quot;); pAdvertising-&gt;addServiceUUID(NimBLEUUID(&quot;4fafc201-1fb5-459e-8fcc-c5c9c331914b&quot;)); advertisementData.setFlags(0x06); // 通用可发现 + 不支持 BR/EDR pAdvertising-&gt;setAdvertisementData(advertisementData); // 设置扫描响应(增强名字可见性) NimBLEAdvertisementData scanResponseData; scanResponseData.setName(&quot;GMD1032-BLE-Slave&quot;); pAdvertising-&gt;setScanResponseData(scanResponseData); pAdvertising-&gt;start(); Serial.println(&quot;✅ BLE 广播已启动,等待连接... (运行于 Core 1)&quot;); for (;;) { if (deviceConnected) { updateBLECharacteristic(); } delay(1000); } } // ==================== setup() ==================== void setup() { Serial.begin(115200); while (!Serial &amp;&amp; millis() &lt; 3000); #ifdef DEBUG_GMD1032 Serial.println(&quot;[DEBUG] GMD1032 调试模式已启用&quot;); #endif // === 初始化 SPI === pinMode(CS_PIN, OUTPUT); digitalWrite(CS_PIN, HIGH); SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN); // === 创建两个独立任务,分别绑定到不同核心 === xTaskCreatePinnedToCore(sensorTask, &quot;SensorTask&quot;, 4096, NULL, 1, NULL, 0); // Core 0 xTaskCreatePinnedToCore(bleTask, &quot;BleTask&quot;, 4096, NULL, 2, NULL, 1); // Core 1 } // ==================== loop() ==================== void loop() { // 所有功能由 FreeRTOS 任务接管,loop 留空 } 主机输出: ESP-ROM:esp32s3-20210327 Build:Mar 27 2021 rst:0x1 (POWERON),boot:0x28 (SPI_FAST_FLASH_BOOT) SPIWP:0xee mode:DIO, clock div:1 load:0x3fce3808,len:0x4bc load:0x403c9700,len:0xbd8 load:0x403cc700,len:0x2a0c entry 0x403c98d0 uart1_init... baud_rate=115200,rx= 5,tx= 4 🔄 Initializing BLE Master (NimBLE)... 🔍 Starting continuous BLE scan... 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-87 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-82 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-55 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-52 🔍 Scanned: [75:73:22:71:43:11], RSSI=-86 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-86 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-88 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-53 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-55 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-84 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-81 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-56 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-79 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-79 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-74 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-86 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-78 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-57 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-73 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-88 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-53 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-74 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-77 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-82 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-60 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-78 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-81 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-57 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-60 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-81 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-75 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-64 🔍 Scanned: [75:73:22:71:43:11], RSSI=-85 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-81 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-78 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-64 🔍 Scanned: [75:73:22:71:43:11], RSSI=-88 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-85 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-81 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-72 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-60 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-75 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-84 🔍 Scanned: [75:73:22:71:43:11], RSSI=-86 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-72 🔍 Scanned: [75:73:22:71:43:11], RSSI=-87 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-81 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-60 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-77 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-84 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-61 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-65 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-81 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-75 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-78 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-78 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-93 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-63 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-59 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-79 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-82 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-85 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-59 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-79 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-79 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-77 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-75 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-58 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-78 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-76 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-83 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-58 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-62 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-81 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-83 🔍 Scanned: [19:08:52:a1:46:57], RSSI=-62 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-85 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-58 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-79 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-86 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-75 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-85 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-58 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-83 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-62 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-76 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-77 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [75:73:22:71:43:11], RSSI=-86 🔍 Scanned: [04:88:56:c5:1f:d6], RSSI=-76 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-75 🔍 Scanned: [75:73:22:71:43:11], RSSI=-88 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-76 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-77
最新发布
10-12
主机: #include &ldquo;BLE_master.h&rdquo; #include &lt;Arduino.h&gt; // ==================== 静态成员定义 ==================== const char* BLEMaster::DEVICE_NAME = &ldquo;GMD1032-BLE-Slave&rdquo;; const char* BLEMaster::TARGET_SERVICE_UUID = &ldquo;4fafc201-1fb5-459e-8fcc-c5c9c331914b&rdquo;; const char* BLEMaster::TARGET_CHAR_UUID = &ldquo;beb5483e-36e1-4688-b7f5-ea07361b26a8&rdquo;; bool BLEMaster::connected = false; NimBLEClient* BLEMaster::pClient = nullptr; BLERemoteCharacteristic* BLEMaster::pRemoteCharacteristic = nullptr; NimBLEAdvertisedDevice* BLEMaster::targetDevice = nullptr; void (BLEMaster::dataCallback)(const char) = nullptr; bool BLEMaster::shouldConnect = false; bool BLEMaster::isConnecting = false; // ==================== 开始扫描 ==================== void BLEMaster::begin() { Serial.println(&ldquo;🔄 Initializing BLE Master (NimBLE)&hellip;&rdquo;); NimBLEDevice::init(&quot;&quot;); NimBLEDevice::deleteAllBonds(); NimBLEScan* pScan = NimBLEDevice::getScan(); pScan-&gt;setScanCallbacks(new ScanCallback(), true); pScan-&gt;setActiveScan(true); pScan-&gt;setInterval(134.5f); pScan-&gt;setWindow(44.8f); Serial.println(&quot;🔍 Starting continuous BLE scan...&quot;); pScan-&gt;start(0, true); // 连续扫描 } // ==================== 设置数据回调 ==================== void BLEMaster::onDataReceived(void (callback)(const char)) { dataCallback = callback; } // ==================== 主循环:状态检查与连接控制 ==================== void BLEMaster::loop() { if (connected &amp;&amp; pClient &amp;&amp; pClient-&gt;isConnected()) { return; // 已连接,无需处理 } if (isConnecting) { return; // 正在连接,防止重复尝试 } NimBLEScan* pScan = NimBLEDevice::getScan(); // 如果扫描结束且标记了连接请求,则发起连接 if (shouldConnect &amp;&amp; targetDevice != nullptr &amp;&amp; !pScan-&gt;isScanning()) { isConnecting = true; connectToDevice(); shouldConnect = false; return; } // 如果长时间未连接,重启扫描 static unsigned long lastScanStart = 0; if (!pScan-&gt;isScanning() &amp;&amp; !connected &amp;&amp; millis() - lastScanStart &gt; 5000) { Serial.println(&quot;🔁 Restarting BLE scan...&quot;); pScan-&gt;start(30, false); lastScanStart = millis(); } } // ==================== 尝试连接目标设备 ==================== bool BLEMaster::connectToDevice() { if (!targetDevice) { Serial.println(&ldquo;❌ No target device to connect.&rdquo;); isConnecting = false; return false; } Serial.printf(&quot;👉 Connecting to: %s [MAC: %s]\n&quot;, targetDevice-&gt;getName().c_str(), targetDevice-&gt;getAddress().toString().c_str()); pClient = NimBLEDevice::createClient(); pClient-&gt;setClientCallbacks(new ClientCallback()); // 快速连接参数 (minInterval=6, maxInterval=12, timeout=100ms) pClient-&gt;setConnectionParams(0x06, 0x0C, 0, 0x64); // connect() 参数说明: // advertiseDevice: 扫描到的设备指针 // deleteAttrs: true 表示每次重新发现服务 // isDeleteCall: false // exchangeMTU: true &rarr; 自动协商大包传输 if (!pClient-&gt;connect(targetDevice, true, false, true)) { Serial.println(&quot;❌ Connection failed!&quot;); cleanupOnFailure(); return false; } Serial.println(&quot;✅ Connected! Discovering service...&quot;); BLERemoteService* pRemoteService = pClient-&gt;getService(NimBLEUUID(TARGET_SERVICE_UUID)); if (!pRemoteService) { Serial.println(&quot;❌ Service not found&quot;); cleanupOnFailure(); return false; } pRemoteCharacteristic = pRemoteService-&gt;getCharacteristic(NimBLEUUID(TARGET_CHAR_UUID)); if (!pRemoteCharacteristic) { Serial.println(&quot;❌ Characteristic not found&quot;); cleanupOnFailure(); return false; } requestMaxMTU(); // 启用通知 if (pRemoteCharacteristic-&gt;canNotify()) { bool subscribed = pRemoteCharacteristic-&gt;subscribe(true, [](BLERemoteCharacteristic* pBLERC, uint8_t* pData, size_t length, bool isNotify) { char jsonStr[512] = {0}; size_t copyLen = min(length, sizeof(jsonStr) - 1); memcpy(jsonStr, pData, copyLen); jsonStr[copyLen] = &#39;\0&#39;; if (dataCallback) { dataCallback((const char*)jsonStr); } else { Serial.printf(&quot;[BLE] Received (%u bytes): %.*s\n&quot;, length, copyLen, jsonStr); } }); if (subscribed) { Serial.println(&quot;🔔 Notify enabled.&quot;); } else { Serial.println(&quot;⚠️ Subscribe failed!&quot;); } } else { Serial.println(&quot;❌ Characteristic does not support notification.&quot;); } connected = true; isConnecting = false; return true; } // ==================== 清理失败状态 ==================== void BLEMaster::cleanupOnFailure() { if (pClient) { pClient-&gt;disconnect(); pClient = nullptr; } connected = false; pRemoteCharacteristic = nullptr; isConnecting = false; } // ==================== 请求最大 MTU ==================== void BLEMaster::requestMaxMTU() { if (pClient &amp;&amp; pClient-&gt;isConnected()) { uint16_t mtu = pClient-&gt;getMTU(); Serial.printf(&ldquo;📈 Current MTU: %u\n&rdquo;, mtu); if (!pClient-&gt;exchangeMTU()) { Serial.println(&quot;⚠️ MTU exchange failed!&quot;); } else { Serial.println(&quot;📤 MTU request sent (negotiating up to 512)&quot;); } } } // ==================== 广播设备回调 ==================== void BLEMaster::ScanCallback::onResult(const NimBLEAdvertisedDevice* advertisedDevice) { Serial.printf(&ldquo;🔍 Scanned: %s [%s], RSSI=%d\n&rdquo;, advertisedDevice-&gt;getName().c_str(), advertisedDevice-&gt;getAddress().toString().c_str(), advertisedDevice-&gt;getRSSI()); if (advertisedDevice-&gt;isAdvertisingService(NimBLEUUID(BLEMaster::TARGET_SERVICE_UUID))) { Serial.printf(&quot;✅ Target device found via UUID: %s [RSSI=%d]\n&quot;, advertisedDevice-&gt;getName().c_str(), advertisedDevice-&gt;getRSSI()); if (BLEMaster::targetDevice) { delete BLEMaster::targetDevice; } BLEMaster::targetDevice = new NimBLEAdvertisedDevice(*advertisedDevice); BLEMaster::shouldConnect = true; NimBLEDevice::getScan()-&gt;stop(); // 停止扫描 } } // ✅ 正确实现 onScanEnd:两个参数,const 引用 void BLEMaster::ScanCallback::onScanEnd(const NimBLEScanResults&amp; scanResults, int reason) { Serial.printf(&ldquo;🏁 Scan ended. Found %d devices. Reason code: %d\n&rdquo;, scanResults.getCount(), reason); // 可在此添加逻辑,例如重新开始扫描 // NimBLEDevice::getScan()-&gt;start(30, false); } // ==================== 客户端连接回调 ==================== void BLEMaster::ClientCallback::onConnect(NimBLEClient* pClient) { Serial.println(&ldquo;✔ BLE Device connected.&rdquo;); } void BLEMaster::ClientCallback::onDisconnect(NimBLEClient* pClient, int reason) { char addr[64]; snprintf(addr, sizeof(addr), &ldquo;%s&rdquo;, pClient-&gt;getPeerAddress().toString().c_str()); Serial.printf(&ldquo;🔌 Disconnected from %s, reason=%d\n&rdquo;, addr, reason); BLEMaster::connected = false; BLEMaster::pRemoteCharacteristic = nullptr; BLEMaster::pClient = nullptr; // 可在此添加延时后自动重连逻辑 } void BLEMaster::ClientCallback::onMTUChange(NimBLEClient* pClient, uint16_t MTU) { Serial.printf(&ldquo;📊 MTU changed to: %u\n&rdquo;, MTU); } 从机: #include &lt;SPI.h&gt; #include &lt;NimBLEDevice.h&gt; // 替换 BLEDevice.h #include &lt;NimBLE2904.h&gt; // 替换 BLE2902.h #include &lt;Arduino.h&gt; // ==================== 引脚定义 ==================== #define SCK_PIN 13 #define MISO_PIN 11 #define MOSI_PIN 14 #define CS_PIN 12 // ==================== 启用调试模式 ==================== #define DEBUG_GMD1032 // ==================== GMD1032 命令列表 ==================== const uint16_t CMD_START_MEASUREMENT = 0x2A16; const uint16_t CMD_CLEAR_ADC_RESULTS = 0x243C; const uint16_t CMD_READ_CFG0 = 0xC08E; const uint16_t CMD_READ_STS0 = 0xC9B1; // CELL 分组命令 const uint16_t CMD_READ_CELL_1_3 = 0xD0FE; const uint16_t CMD_READ_CELL_4_6 = 0xD1F9; const uint16_t CMD_READ_CELL_7_9 = 0xD2F0; const uint16_t CMD_READ_CELL_10_12 = 0xD3F7; const uint16_t CMD_READ_CELL_13_15 = 0xD4E2; const uint16_t CMD_READ_CELL_16_18 = 0xD5E5; // DIS 分组命令 const uint16_t CMD_READ_DIS_1_3 = 0xD6EC; const uint16_t CMD_READ_DIS_4_6 = 0xD7EB; const uint16_t CMD_READ_DIS_7_9 = 0xD8C6; const uint16_t CMD_READ_DIS_10_12 = 0xD9C1; const uint16_t CMD_READ_DIS_13_15 = 0xDAC8; const uint16_t CMD_READ_DIS_16_18 = 0xDBCF; // 其他测量命令 const uint16_t CMD_READ_VREF2_V3 = 0xDFD3; const uint16_t CMD_READ_V5_DIETEMP_VSTK = 0xE06E; // ==================== SPI 设置 ==================== SPISettings spiSettings(1000000, MSBFIRST, SPI_MODE3); // 1MHz, Mode3 (CPOL=1, CPHA=1) // ==================== BLE 配置 ==================== #define SERVICE_UUID &ldquo;4fafc201-1fb5-459e-8fcc-c5c9c331914b&rdquo; #define CHARACTERISTIC_UUID &ldquo;beb5483e-36e1-4688-b7f5-ea07361b26a8&rdquo; NimBLECharacteristic* pCharacteristic = nullptr; // 改为 NimBLE 类型 bool deviceConnected = false; // 共享数据缓冲区 float sharedCellVoltages[16] = {0}; float sharedDisVoltages[16] = {0}; float sharedVref2 = 0.0f, sharedV3 = 0.0f, sharedV5 = 0.0f, sharedDieTemp = 0.0f, sharedVstk = 0.0f; portMUX_TYPE sensorMutex = portMUX_INITIALIZER_UNLOCKED; // 保护共享数据 // ==================== 回调类声明 ==================== class MyServerCallbacks : public NimBLEServerCallbacks { // 正确签名:使用 NimBLEConnInfo&amp; void onConnect(NimBLEServer* pServer, NimBLEConnInfo&amp; connInfo) override { deviceConnected = true; Serial.println(&ldquo;设备接入~&rdquo;); } // 正确签名:包含 connInfo 和 reason void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo&amp; connInfo, int reason) override { deviceConnected = false; Serial.print(&quot;设备断开~ 原因代码: &quot;); Serial.println(reason); pServer-&gt;startAdvertising(); // 重新开始广播 } }; class MyCharacteristicCallbacks : public NimBLECharacteristicCallbacks { // 推荐加 const,并保留 override void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo&amp; connInfo) override { std::string value = pCharacteristic-&gt;getValue(); if (!value.empty()) { Serial.printf(&ldquo;📥 客户端 %s 写入数据: %s\n&rdquo;, std::string(connInfo.getAddress()).c_str(), // 正确调用 value.c_str()); } } }; // ==================== 辅助函数:打印二进制 ==================== void printBinary(uint8_t b) { for (int i = 7; i &gt;= 0; i&ndash;) { Serial.write((b &amp; (1 &lt;&lt; i)) ? &lsquo;1&rsquo; : &lsquo;0&rsquo;); } } // ==================== 唤醒芯片 ==================== void wakeUpGMD1032() { SPI.beginTransaction(spiSettings); digitalWrite(CS_PIN, LOW); delayMicroseconds(10); SPI.transfer(0x00); digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); delay(5); } // ==================== 发送命令 ==================== bool sendCommand(uint16_t command) { SPI.beginTransaction(spiSettings); digitalWrite(CS_PIN, LOW); SPI.transfer16(command); digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); return true; } // ==================== 解析逆序字段 ==================== void parseReverseFields(uint8_t* buffer, uint16_t* results, int count) { for (int i = 0; i &lt; count; i++) { int bufIdx = i * 2; results[count - 1 - i] = (buffer[bufIdx] &lt;&lt; 8) | buffer[bufIdx + 1]; } } // ==================== 读取所有 CELL 电压 ==================== bool readAllCellVoltages(float* cellVoltages) { const uint16_t cmds[] = { CMD_READ_CELL_1_3, CMD_READ_CELL_4_6, CMD_READ_CELL_7_9, CMD_READ_CELL_10_12, CMD_READ_CELL_13_15, CMD_READ_CELL_16_18 }; const int groupCount = 6; const int perGroup[] = {3, 3, 3, 3, 3, 3}; SPI.beginTransaction(spiSettings); for (int g = 0; g &lt; groupCount; g++) { digitalWrite(CS_PIN, LOW); SPI.transfer16(cmds[g]); uint8_t buffer[8]; for (int i = 0; i &lt; 8; i++) { buffer[i] = SPI.transfer(0x00); } digitalWrite(CS_PIN, HIGH); uint16_t raw[3]; parseReverseFields(buffer, raw, perGroup[g]); for (int i = 0; i &lt; perGroup[g]; i++) { int idx = g * 3 + i; if (idx &gt;= 16) continue; float voltage = (raw[i] - 10240) * 0.0001f; cellVoltages[idx] = voltage; } } SPI.endTransaction(); return true; } // ==================== 读取所有 DIS 电压 ==================== bool readAllDisVoltages(float* disVoltages) { const uint16_t cmds[] = { CMD_READ_DIS_1_3, CMD_READ_DIS_4_6, CMD_READ_DIS_7_9, CMD_READ_DIS_10_12, CMD_READ_DIS_13_15, CMD_READ_DIS_16_18 }; const int groupCount = 6; const int perGroup[] = {3, 3, 3, 3, 3, 3}; SPI.beginTransaction(spiSettings); for (int g = 0; g &lt; groupCount; g++) { digitalWrite(CS_PIN, LOW); SPI.transfer16(cmds[g]); uint8_t buffer[8]; for (int i = 0; i &lt; 8; i++) { buffer[i] = SPI.transfer(0x00); } digitalWrite(CS_PIN, HIGH); uint16_t raw[3]; parseReverseFields(buffer, raw, perGroup[g]); for (int i = 0; i &lt; perGroup[g]; i++) { int idx = g * 3 + i; if (idx &gt;= 16) continue; float voltage = (raw[i] - 10240) * 0.0001f; disVoltages[idx] = voltage; } } SPI.endTransaction(); return true; } // ==================== 读取 VREF2 和 V3 ==================== bool readVref2AndV3(float* vref2, float* v3) { uint8_t buffer[8]; SPI.beginTransaction(spiSettings); digitalWrite(CS_PIN, LOW); SPI.transfer16(CMD_READ_VREF2_V3); for (int i = 0; i &lt; 8; i++) { buffer[i] = SPI.transfer(0x00); } digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); uint16_t raw_v3 = (buffer[0] &lt;&lt; 8) | buffer[1]; uint16_t raw_vref2 = (buffer[2] &lt;&lt; 8) | buffer[3]; *v3 = (raw_v3 - 10240) * 0.0001f; *vref2 = (raw_vref2 - 10240) * 0.0001f; return true; } // ==================== 读取 V5 / DieTemp / VSTK ==================== bool readV5DieTempVStk(float* v5, float* dieTemp, float* vstk) { uint8_t buffer[8]; SPI.beginTransaction(spiSettings); digitalWrite(CS_PIN, LOW); SPI.transfer16(CMD_READ_V5_DIETEMP_VSTK); for (int i = 0; i &lt; 8; i++) { buffer[i] = SPI.transfer(0x00); } digitalWrite(CS_PIN, HIGH); SPI.endTransaction(); uint16_t raw_vstk = (buffer[0] &lt;&lt; 8) | buffer[1]; uint16_t raw_dieTemp = (buffer[2] &lt;&lt; 8) | buffer[3]; uint16_t raw_v5 = (buffer[4] &lt;&lt; 8) | buffer[5]; *vstk = (raw_vstk - 10240) * 0.0016f; *v5 = (raw_v5 - 10240) * 0.0001f; if (raw_dieTemp &gt;= 8000 &amp;&amp; raw_dieTemp &lt;= 30000) { *dieTemp = raw_dieTemp * 0.27662 - 3105.59; } else { *dieTemp = NAN; } return true; } // ==================== 更新 BLE 特征值(从 Core 1 调用)==================== void updateBLECharacteristic() { static char jsonBuffer[512]; int offset = 0; float cell[16]; float dieTemp; // 安全拷贝共享数据 portENTER_CRITICAL(&amp;sensorMutex); memcpy(cell, sharedCellVoltages, sizeof(cell)); dieTemp = sharedDieTemp; portEXIT_CRITICAL(&amp;sensorMutex); // 构造 JSON: {&quot;cell&quot;:[...], &quot;temp&quot;: ...} offset += snprintf(jsonBuffer + offset, sizeof(jsonBuffer) - offset, &quot;{\&quot;cell\&quot;:[&quot;); for (int i = 0; i &lt; 16; i++) { offset += snprintf(jsonBuffer + offset, sizeof(jsonBuffer) - offset, &quot;%.3f%s&quot;, cell[i], (i &lt; 15) ? &quot;,&quot; : &quot;&quot;); } if (!isnan(dieTemp)) { offset += snprintf(jsonBuffer + offset, sizeof(jsonBuffer) - offset, &quot;],\&quot;temp\&quot;:%.2f}&quot;, dieTemp); } else { offset += snprintf(jsonBuffer + offset, sizeof(jsonBuffer) - offset, &quot;],\&quot;temp\&quot;:null}&quot;); } // 防溢出兜底 if (offset &gt;= sizeof(jsonBuffer)) { strcpy(jsonBuffer, &quot;{\&quot;err\&quot;:\&quot;long\&quot;}&quot;); } // 发送到 BLE if (pCharacteristic &amp;&amp; deviceConnected) { pCharacteristic-&gt;setValue(jsonBuffer); pCharacteristic-&gt;notify(); // 触发通知 } #ifdef DEBUG_GMD1032 Serial.printf(&ldquo;BLE Data: %s\n&rdquo;, jsonBuffer); #endif } // ==================== 核心 0:传感器采集任务 ==================== void sensorTask(void *parameter) { for (;😉 { yield(); wakeUpGMD1032(); delay(2); sendCommand(CMD_START_MEASUREMENT); delay(10); float cellVoltages[16] = {0}; float disVoltages[16] = {0}; float vref2 = 0.0f, v3 = 0.0f, v5 = 0.0f, dieTemp = 0.0f, vstk = 0.0f; readAllCellVoltages(cellVoltages); readAllDisVoltages(disVoltages); readVref2AndV3(&amp;vref2, &amp;v3); readV5DieTempVStk(&amp;v5, &amp;dieTemp, &amp;vstk); // 写入共享变量(加锁) portENTER_CRITICAL(&amp;sensorMutex); memcpy(sharedCellVoltages, cellVoltages, sizeof(cellVoltages)); memcpy(sharedDisVoltages, disVoltages, sizeof(disVoltages)); sharedVref2 = vref2; sharedV3 = v3; sharedV5 = v5; sharedDieTemp = dieTemp; sharedVstk = vstk; portEXIT_CRITICAL(&amp;sensorMutex); #ifdef DEBUG_GMD1032 Serial.println(&ldquo;============== GMD1032 全通道数据 ==============&rdquo;); for (int i = 0; i &lt; 16; i++) { if (i % 4 == 0) { Serial.print(&quot; &quot;); } Serial.printf(&quot;CELL%2d:%5.3fV &quot;, i + 1, cellVoltages[i]); if ((i + 1) % 4 == 0 || i == 15) { Serial.println(); } } if (!isnan(dieTemp)) { Serial.printf(&quot;芯片温度: %.2f&deg;C\n&quot;, dieTemp); } else { Serial.println(&quot;芯片温度: 无效数据&quot;); } Serial.println(&quot;---------------------------------------------&quot;); #endif delay(1000); // 每秒采集一次 } } // ==================== 核心 1:BLE 服务任务 ==================== void bleTask(void parameter) { NimBLEDevice::init(&ldquo;GMD1032-BLE-Slave&rdquo;); NimBLEServer pServer = NimBLEDevice::createServer(); pServer-&gt;setCallbacks(new MyServerCallbacks()); NimBLEService* pService = pServer-&gt;createService(SERVICE_UUID); pCharacteristic = pService-&gt;createCharacteristic( CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY ); pCharacteristic-&gt;addDescriptor(new NimBLE2904()); pCharacteristic-&gt;setCallbacks(new MyCharacteristicCallbacks()); pCharacteristic-&gt;setValue(&quot;GMD1032 Ready&quot;); pService-&gt;start(); NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); // 显式设置广告内容 NimBLEAdvertisementData advertisementData; advertisementData.setName(&quot;GMD1032-BLE-Slave&quot;); pAdvertising-&gt;addServiceUUID(NimBLEUUID(&quot;4fafc201-1fb5-459e-8fcc-c5c9c331914b&quot;)); advertisementData.setFlags(0x06); // 通用可发现 + 不支持 BR/EDR pAdvertising-&gt;setAdvertisementData(advertisementData); // 设置扫描响应(增强名字可见性) NimBLEAdvertisementData scanResponseData; scanResponseData.setName(&quot;GMD1032-BLE-Slave&quot;); pAdvertising-&gt;setScanResponseData(scanResponseData); pAdvertising-&gt;start(); Serial.println(&quot;✅ BLE 广播已启动,等待连接... (运行于 Core 1)&quot;); for (;;) { if (deviceConnected) { updateBLECharacteristic(); } delay(1000); } } // ==================== setup() ==================== void setup() { Serial.begin(115200); while (!Serial &amp;&amp; millis() &lt; 3000); #ifdef DEBUG_GMD1032 Serial.println(&ldquo;[DEBUG] GMD1032 调试模式已启用&rdquo;); #endif // === 初始化 SPI === pinMode(CS_PIN, OUTPUT); digitalWrite(CS_PIN, HIGH); SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN); // === 创建两个独立任务,分别绑定到不同核心 === xTaskCreatePinnedToCore(sensorTask, &quot;SensorTask&quot;, 4096, NULL, 1, NULL, 0); // Core 0 xTaskCreatePinnedToCore(bleTask, &quot;BleTask&quot;, 4096, NULL, 2, NULL, 1); // Core 1 } // ==================== loop() ==================== void loop() { // 所有功能由 FreeRTOS 任务接管,loop 留空 } 主机输出: ESP-ROM:esp32s3-20210327 Build:Mar 27 2021 rst:0x1 (POWERON),boot:0x28 (SPI_FAST_FLASH_BOOT) SPIWP:0xee mode:DIO, clock div:1 load:0x3fce3808,len:0x4bc load:0x403c9700,len:0xbd8 load:0x403cc700,len:0x2a0c entry 0x403c98d0 uart1_init... baud_rate=115200,rx= 5,tx= 4 🔄 Initializing BLE Master (NimBLE)... 🔍 Starting continuous BLE scan... 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-85 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-83 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-84 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-56 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-87 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-51 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-92 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-85 🔍 Scanned: [75:73:22:71:43:11], RSSI=-90 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-84 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-81 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-76 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-51 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-85 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-86 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-91 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-87 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-74 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-80 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-87 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-60 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-56 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-84 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-90 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-87 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-86 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-61 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-89 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-85 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-80 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-86 🔍 Scanned: GMD1032-BLE-Slave [b8:f8:62:c8:b4:dd], RSSI=-83 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-51 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-56 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-91 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-86 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-83 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-56 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-84 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-85 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-77 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-93 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-89 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-53 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-84 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-75 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-79 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-91 🔍 Scanned: [21:6a:3e:dd:db:c2], RSSI=-90 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-53 🔍 Scanned: [2f:c6:42:20:73:b8], RSSI=-52 🔍 Scanned: [75:73:22:71:43:11], RSSI=-91 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-80 🔍 Scanned: [35:f6:52:cf:5d:6f], RSSI=-85 🔍 Scanned: [11:3a:9e:52:86:70], RSSI=-85
10-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值