当前位置: 首页 > news >正文

从零到精通:嵌入式BLE开发实战指南

1. BLE的魅力与核心概念:为什么选低功耗蓝牙?

低功耗蓝牙(Bluetooth Low Energy,简称BLE)是物联网设备的宠儿。相比经典蓝牙,BLE以超低功耗灵活性著称,非常适合智能手环、传感器、医疗设备等场景。它的核心在于间歇性通信:设备大部分时间处于睡眠状态,仅在需要时短暂唤醒发送数据,省电到能让一颗纽扣电池撑上几年!

BLE的核心术语

  • GATT(Generic Attribute Profile):BLE的数据通信基石,定义了客户端和服务器如何交换数据。

  • 服务(Service)与特征(Characteristic):服务是一组功能的集合,特征是具体的功能单元,比如心率服务包含一个心率特征。

  • UUID:每个服务和特征都有唯一标识符,128位UUID是自定义服务的标配。

  • 广播(Advertising):BLE设备通过广播告诉周围“我在这儿”,可以附带少量数据。

  • 连接(Connection):设备建立连接后,可以进行更复杂的数据交互。

为什么选择BLE?

  • 低功耗:一个CR2032电池能让设备运行数月甚至数年。

  • 简单协议:GATT结构清晰,开发门槛低。

  • 广泛支持:从手机到嵌入式设备,BLE几乎无处不在。

小Tips:BLE的广播模式适合快速传输小数据包,比如温度传感器每分钟广播一次读数。如果需要实时大数据传输(像音频流),经典蓝牙或Wi-Fi可能更合适。

2. 硬件选型:选择合适的BLE芯片

BLE开发的起点是硬件。市面上的BLE芯片琳琅满目,选对芯片能事半功倍。以下是几款主流芯片的对比和适用场景:

热门BLE芯片推荐

  1. Nordic nRF52840

    • 优势:支持BLE 5.0,带Cortex-M4F内核,512KB Flash,功耗低,支持Mesh网络。

    • 适用场景:复杂物联网设备,如智能家居控制器。

    • 注意:开发板(如nRF52840-DK)调试方便,但芯片引脚多,初学者焊接需小心。

  2. TI CC2640R2F

    • 优势:超低功耗,集成天线设计,适合小型设备。

    • 适用场景:穿戴设备、医疗传感器。

    • 注意:TI的协议栈文档复杂,建议用TI官方例程起步。

  3. Silicon Labs EFR32BG22

    • 优势:低成本,易于上手,支持BLE 5.2。

    • 适用场景:预算有限的初创项目。

    • 注意:社区资源较少,需依赖官方SDK。

选型时的关键考量

  • 功耗:查看芯片的睡眠模式电流(通常在nA级别)和活跃模式电流。

  • 存储空间:确保Flash和RAM足够存放协议栈和你的应用代码。

  • 外设支持:检查是否有足够的GPIO、ADC或I2C接口满足你的传感器需求。

  • 开发工具:优先选择有成熟IDE和调试工具的芯片,比如Nordic的SES(Segger Embedded Studio)。

实战案例:假设你要开发一个BLE温度传感器,nRF52840是不错的选择。它的ADC精度高,能轻松连接DS18B20温度传感器,同时支持长距离广播(BLE 5.0的Coded PHY可达1km)。

小Tips:别只看芯片参数,开发板的生态也很重要!Nordic的nRF Connect SDK提供了丰富的例程和调试工具,能帮你快速验证想法。

3. 开发环境搭建:让你的代码跑起来

工欲善其事,必先利其器。BLE开发的工具链直接影响效率。以下是搭建开发环境的详细步骤,以Nordic nRF52840为例。

工具准备

  1. IDE:推荐Segger Embedded Studio(SES),Nordic官方支持,免费且轻量。

  2. SDK:下载nRF Connect SDK(支持Zephyr RTOS,功能强大)。

  3. 调试器:J-Link调试器(nRF52840-DK自带)。

  4. 手机App:nRF Connect(iOS/Android),用于测试BLE广播和连接。

安装步骤

  1. 安装SES:从Segger官网下载,安装后导入nRF Connect SDK。

  2. 配置SDK

    • 下载nRF Connect SDK(版本建议v2.5.0或更高)。

    • 设置环境变量,确保nrfjprog和west命令可用。

  3. 验证环境

    • 打开SES,加载SDK中的blinky例程。

    • 连接nRF52840-DK,点击“Build and Run”,检查板载LED是否闪烁。

常见问题解决

  • 编译失败:检查SDK路径是否正确,确认CMake版本兼容。

  • 设备不识别:确保J-Link驱动已安装,USB线连接稳定。

  • 功耗异常:调试时关闭不必要的串口输出,串口打印非常耗电!

代码示例:以下是一个简单的BLE广播程序,设备每秒广播一次自定义数据。

#include <zephyr.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>static const struct bt_data ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),BT_DATA(BT_DATA_NAME_COMPLETE, "MyBLEDevice", 11),
};void main(void) {int err;err = bt_enable(NULL);if (err) {printk("Bluetooth init failed (err %d)\n", err);return;}printk("Bluetooth initialized\n");err = bt_le_adv_start(BT_LE_ADV_NCONN, ad, ARRAY_SIZE(ad), NULL, 0);if (err) {printk("Advertising failed to start (err %d)\n", err);return;}printk("Advertising started\n");
}

运行效果:用nRF Connect App扫描,搜索“MyBLEDevice”,你会看到设备广播的名称。

小Tips:调试时用手机App扫描BLE信号,比直接看代码输出更直观。记得检查RSSI(信号强度),RSSI<-90dBm说明距离太远或有干扰。

4. 深入GATT:打造你的BLE服务

BLE的核心是GATT协议,它定义了设备间如何交换数据。设计一个自定义服务是BLE开发的必修课。以下是一个创建自定义温度服务的实战案例。

服务设计

假设我们要创建一个温度传感器服务,包含一个只读特征,用于发送温度值。

  • 服务UUID:12345678-1234-5678-1234-56789abcdef0

  • 特征UUID:12345678-1234-5678-1234-56789abcdef1

  • 属性:只读,通知(Notify)启用。

代码实现

以下是基于Zephyr RTOS的实现代码:

#include <zephyr.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/gatt.h>
#include <sys/byteorder.h>static uint16_t temp_value = 2500; // 温度值,25.00°C,放大100倍存储static ssize_t read_temp(struct bt_conn *conn, const struct bt_gatt_attr *attr,void *buf, uint16_t len, uint16_t offset) {uint16_t val = sys_cpu_to_le16(temp_value);return bt_gatt_attr_read(conn, attr, buf, len, offset, &val, sizeof(val));
}BT_GATT_SERVICE_DEFINE(temp_service,BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_128(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12)),BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(0xf1, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12),BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,BT_GATT_PERM_READ, read_temp, NULL, &temp_value),
);void main(void) {int err = bt_enable(NULL);if (err) {printk("Bluetooth init failed (err %d)\n", err);return;}printk("Bluetooth initialized\n");
}

代码解析

  • 服务定义:BT_GATT_SERVICE_DEFINE宏定义了一个服务,包含一个主服务和一个特征。

  • 特征读操作:read_temp函数处理客户端读取温度值的请求,温度值以小端格式返回。

  • UUID:128位UUID用16字节数组表示,注意字节序。

测试方法

  1. 用nRF Connect App连接设备。

  2. 找到自定义服务(UUID以1234开头)。

  3. 读取特征值,应返回2500(即25.00°C)。

小Tips:自定义UUID时,建议用在线UUID生成工具,确保唯一性。BLE调试时,抓包工具(如nRF Sniffer)能帮你看清数据交互细节。

5. 协议栈优化:让BLE更快更稳

BLE的协议栈是整个系统的核心,优化得好,能让你的设备响应更快、功耗更低、连接更稳定。Zephyr RTOS和Nordic的SoftDevice提供了灵活的配置选项,但稍不注意就可能踩坑。接下来,我们聊聊如何通过调整参数和代码逻辑,提升BLE性能。

关键优化点

  • 连接间隔(Connection Interval):这是BLE设备通信的“心跳”,决定了数据交换的频率。范围通常在7.5ms到4s之间。

    • 短间隔(如7.5ms):适合实时性要求高的场景,比如键盘输入,但耗电多。

    • 长间隔(如1s):适合低频数据传输,比如环境传感器,省电但延迟高。

  • MTU大小:BLE默认MTU是23字节(包括3字节头部)。增大MTU能提升吞吐量,但会增加功耗和内存占用。

  • 广播参数:广播间隔和数据包内容直接影响设备被发现的速度和功耗。

实战:调整连接间隔

假设你的BLE设备是一个心率监测器,需要每秒传输一次数据。我们将连接间隔设为500ms,平衡实时性和功耗。

#include <zephyr.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>static void connected(struct bt_conn *conn, uint8_t err) {if (err) {printk("Connection failed (err %u)\n", err);return;}printk("Connected\n");struct bt_le_conn_param param = {.interval_min = 400, // 500ms (400 * 1.25ms).interval_max = 400,.latency = 0,.timeout = 400, // 4s (400 * 10ms)};err = bt_conn_le_param_update(conn, &param);if (err) {printk("Connection parameter update failed (err %d)\n", err);}
}static struct bt_conn_cb conn_callbacks = {.connected = connected,.disconnected = disconnected,
};void main(void) {int err = bt_enable(NULL);if (err) {printk("Bluetooth init failed (err %d)\n", err);return;}bt_conn_cb_register(&conn_callbacks);// 广播代码略(参考第3章)
}

代码解析

  • bt_le_conn_param定义了连接参数,interval_min和interval_max设为400(500ms)。

  • latency设为0,避免延迟累积。

  • timeout是监督超时,设为4s,确保连接断开前有足够的重试时间。

调试技巧:用nRF Connect App查看实际连接间隔(在Connection Information中)。如果手机不支持500ms,可能需要动态调整interval_max。

MTU优化

增大MTU可以减少数据分包,提升吞吐量。以下代码启用MTU协商:

static void mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx) {printk("MTU updated: TX=%d, RX=%d\n", tx, rx);
}static struct bt_gatt_cb gatt_callbacks = {.att_mtu_updated = mtu_updated,
};void main(void) {bt_gatt_cb_register(&gatt_callbacks);// 其他初始化代码略
}

注意:MTU协商需要客户端(比如手机)支持。iOS默认支持251字节,Android可能需要手动配置。

小Tips:调试MTU时,抓包工具能显示实际数据包大小。如果发现吞吐量没提升,检查是否有多余的GATT操作阻塞了传输。

6. 功耗管理:让电池续命到极致

BLE的杀手锏是低功耗,但实际开发中,功耗优化是个精细活。稍不注意,设备可能从“续航一年”变成“几天就没电”。以下是几个关键的功耗优化策略。

策略一:选择合适的广播模式

  • 非连接广播(Non-connectable Advertising):适合只广播数据的设备,比如iBeacon。功耗最低,但无法交互。

  • 可连接广播:允许设备被连接,适合需要交互的场景。广播间隔建议设为100ms到1s。

代码示例

static const struct bt_data ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL),BT_DATA(BT_DATA_NAME_COMPLETE, "HeartMonitor", 12),
};void main(void) {bt_le_adv_start(BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE, 160, 160, NULL),ad, ARRAY_SIZE(ad), NULL, 0);
}

解析:160表示200ms(160 * 1.25ms),适合大多数场景。如果设备只需要广播,改用BT_LE_ADV_OPT_NONE。

策略二:睡眠模式

BLE芯片通常支持多种睡眠模式,比如Nordic nRF52840的System ON Idle(约2µA)和System OFF(约0.3µA)。以下是启用低功耗模式的代码:

#include <zephyr.h>
#include <power/power.h>void main(void) {// 初始化BLEbt_enable(NULL);// 进入低功耗模式sys_set_power_mode(SYS_POWER_STATE_DEEP_SLEEP_1);
}

注意:Zephyr的电源管理需要正确配置设备树(DTS),确保外设在睡眠时关闭。

策略三:减少不必要的外设活动

  • 关闭串口打印:printk非常耗电,调试完成后尽量禁用。

  • 优化传感器采样:比如,只在需要时开启ADC采样。

  • 降低时钟频率:nRF52840支持动态调整CPU频率,空闲时降到16MHz。

实战案例:一个BLE温度传感器每10秒采样一次,广播数据后进入深度睡眠。实测功耗可降到5µA以下,CR2032电池能用2年以上。

小Tips:用电流表(如Nordic的Power Profiler Kit)测量实际功耗,比靠猜靠谱。发现异常高功耗时,检查是否有未关闭的定时器或GPIO。

7. 调试技巧:快速定位BLE问题

BLE开发中,问题无处不在:连接不上、数据丢包、功耗异常……以下是几个调试神技,帮你快速定位问题。

技巧一:用抓包工具

  • 工具推荐:nRF Sniffer + Wireshark

  • 用法:用nRF52840-DK作为嗅探器,捕获BLE数据包。Wireshark会显示广播、连接和GATT交互的细节。

  • 常见问题

    • 广播包不出现:检查广播间隔和天线方向。

    • 连接失败:查看是否是MTU不匹配或加密要求不一致。

技巧二:日志输出

Zephyr的printk虽然耗电,但在调试早期很实用。以下是添加日志的例子:

static void gatt_read_cb(struct bt_conn *conn, uint8_t err, const struct bt_gatt_attr *attr, const void *buf, uint16_t len) {if (err) {printk("GATT read error: %d\n", err);return;}printk("GATT read: %d bytes\n", len);
}

技巧三:手机App辅助

nRF Connect App不仅能扫描设备,还能查看服务、特征和连接参数。遇到问题时,先用App验证设备行为是否符合预期。

真实案例:有次调试时,设备广播正常但无法连接。用nRF Sniffer发现是手机端未正确处理自定义UUID。改用标准UUID后问题解决。

小Tips:调试时,记录每个问题的现象、假设和解决方法。BLE问题往往是多方面因素叠加,日志是你的救命稻草。

8. 固件升级(OTA):让你的BLE设备永葆青春

固件升级(Over-The-Air,简称OTA)是BLE设备保持竞争力的关键。用户无需拆开设备,只通过手机App就能更新功能、修复Bug或优化性能。Nordic的nRF Connect SDK提供了强大的OTA支持,下面我们来一步步实现一个可靠的BLE OTA方案。

OTA的核心流程

  1. 固件准备:生成新的固件镜像(通常是.bin或.hex文件)。

  2. 传输:通过BLE将固件分包传输到设备。

  3. 验证:设备检查固件完整性(比如CRC校验)。

  4. 更新:设备将新固件写入Flash并重启。

实现一个简单的OTA服务

我们基于Zephyr RTOS,使用Nordic的DFU(Device Firmware Update)协议实现OTA。以下是关键代码:

#include <zephyr.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/gatt.h>
#include <mgmt/mcumgr/smp_bt.h>void main(void) {int err;// 初始化蓝牙err = bt_enable(NULL);if (err) {printk("Bluetooth init failed (err %d)\n", err);return;}printk("Bluetooth initialized\n");// 启用SMP服务(用于OTA)err = smp_bt_register();if (err) {printk("SMP service failed to register (err %d)\n", err);return;}printk("SMP service registered\n");// 广播代码(参考第3章)
}

配置Zephyr

要在Zephyr中启用OTA,需要在项目配置文件(prj.conf)中添加以下内容:

CONFIG_MCUMGR=y
CONFIG_MCUMGR_CMD_IMG_MGMT=y
CONFIG_MCUMGR_SMP_BT=y
CONFIG_BOOTLOADER_MCUBOOT=y
  • MCUMGR:Zephyr的管理框架,支持OTA。

  • MCUBOOT:一个开源Bootloader,负责固件切换和回滚。

测试OTA

  1. 生成固件:用SES编译项目,生成.bin文件。

  2. 上传固件:用nRF Connect App的DFU功能,选择.bin文件,连接设备后开始传输。

  3. 验证更新:设备重启后,检查新固件是否生效(比如通过版本号特征)。

注意事项

  • Flash分区:确保设备Flash有足够的存储空间,分为应用区和OTA暂存区。

  • 断电保护:OTA过程中断电可能导致设备变砖,MCUBOOT支持回滚机制,降低风险。

  • 带宽优化:BLE传输速度慢,建议压缩固件或启用更大的MTU(参考第5章)。

实战案例:我在开发一款BLE灯控设备时,OTA功能让客户能远程更新灯光效果。一次更新中,固件传输中断,导致设备卡在Bootloader。通过配置MCUBOOT的回滚功能,设备自动恢复到旧版本,救回了一堆设备!

小Tips:OTA测试时,先用开发板验证流程,避免直接在产品上实验。nRF Connect App的DFU日志能帮你快速定位传输失败的原因。

9. 安全机制:保护你的BLE通信

BLE设备的安全性至关重要,尤其是在医疗、金融或智能家居领域。未加密的通信可能被窃听,甚至被恶意控制。以下是BLE的几大安全机制和实现方法。

安全机制概览

  • 配对(Pairing):设备间建立信任关系,生成长期密钥。

  • 加密(Encryption):使用AES-128加密数据,防止窃听。

  • 认证(Authentication):确保通信双方身份可信。

  • MITM保护:防止中间人攻击,通常通过密码或OOB(带外)认证。

实现一个安全的BLE服务

以下是一个带加密的温度服务,客户端必须配对后才能读取数据:

#include <zephyr.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/gatt.h>static uint16_t temp_value = 2500;static ssize_t read_temp(struct bt_conn *conn, const struct bt_gatt_attr *attr,void *buf, uint16_t len, uint16_t offset) {if (!bt_conn_is_encrypted(conn)) {printk("Connection not encrypted!\n");return BT_GATT_ERR(BT_ATT_ERR_AUTHENTICATION);}uint16_t val = sys_cpu_to_le16(temp_value);return bt_gatt_attr_read(conn, attr, buf, len, offset, &val, sizeof(val));
}BT_GATT_SERVICE_DEFINE(temp_service,BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_16(0x1809)), // 使用标准健康温度服务UUIDBT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_16(0x2A1C),BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,BT_GATT_PERM_READ_ENCRYPT, read_temp, NULL, &temp_value),
);static void auth_passkey_entry(struct bt_conn *conn) {printk("Passkey entry requested\n");// 这里可以提示用户输入密码,比如通过串口或LED
}static struct bt_conn_auth_cb auth_cb = {.passkey_entry = auth_passkey_entry,
};void main(void) {bt_conn_auth_cb_register(&auth_cb);bt_enable(NULL);
}

代码解析

  • BT_GATT_PERM_READ_ENCRYPT:只有加密连接才能读取特征。

  • auth_passkey_entry:处理密码输入配对,适合有显示屏或输入设备的场景。

  • 使用标准UUID(0x1809和0x2A1C)以兼容更多客户端。

测试安全连接

  1. 用nRF Connect App连接设备,尝试读取温度特征。

  2. App会提示输入密码(默认6位数字,可在代码中设置)。

  3. 配对成功后,读取返回2500;未配对会返回错误。

常见问题

  • 配对失败:检查设备是否支持相同的配对方式(比如LE Secure Connections)。

  • 加密性能:加密会略微增加功耗和延迟,需权衡。

小Tips:调试安全问题时,抓包工具能看到配对过程中的密钥交换细节。如果设备没有输入输出能力,考虑用OOB配对(比如通过NFC)。

10. 多设备通信:打造BLE Mesh网络

当你的项目需要多个BLE设备协同工作,比如智能家居的灯控网络,BLE Mesh是个绝佳选择。它允许上千个设备组成网状网络,互相转发消息。Nordic的nRF Mesh SDK提供了强大的支持,下面我们来实现一个简单的Mesh网络。

BLE Mesh基础

  • 节点(Node):网络中的设备,分担不同角色(如中继节点、终端节点)。

  • 模型(Model):定义设备的交互逻辑,比如开关模型。

  • 组播(Group Addressing):消息发送到一组设备,提高效率。

实现一个Mesh开关

以下代码实现一个简单的开关模型,控制LED状态:

#include <zephyr.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/mesh.h>static void gen_onoff_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,struct net_buf_simple *buf) {uint8_t state = net_buf_simple_pull_u8(buf);printk("Set LED state to %d\n", state);// 这里控制LED的GPIO
}static const struct bt_mesh_model_op gen_onoff_op[] = {{ BT_MESH_MODEL_OP_2(0x82, 0x04), 0, gen_onoff_set },BT_MESH_MODEL_OP_END,
};static struct bt_mesh_model root_models[] = {BT_MESH_MODEL_CFG_SRV,BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_op, NULL, NULL),
};static struct bt_mesh_elem elements[] = {BT_MESH_ELEM(0, root_models, BT_MESH_MODEL_NONE),
};static const struct bt_mesh_comp comp = {.elem = elements,.elem_count = ARRAY_SIZE(elements),
};void main(void) {bt_mesh_init(&comp);bt_enable(NULL);
}

代码解析

  • gen_onoff_set:处理开关消息,解析收到的状态值。

  • BT_MESH_MODEL_ID_GEN_ONOFF_SRV:标准通用开关服务。

  • bt_mesh_init:初始化Mesh协议栈。

配置Mesh网络

  1. 配网(Provisioning):用nRF Mesh App为设备分配网络密钥和地址。

  2. 组播设置:将多个设备加入同一组地址,统一控制。

  3. 测试:用App发送开关命令,观察LED状态。

注意事项

  • 网络规模:Mesh网络支持上千节点,但中继节点会增加功耗。

  • 消息延迟:Mesh消息通过多跳转发,延迟可能达几十毫秒。

  • 安全性:Mesh网络默认加密,确保网络密钥安全存储。

实战案例:我曾用BLE Mesh实现了一个10个节点的智能灯网络。每个灯既是终端节点也能中继消息,覆盖了整个办公室。调试时发现,密集环境中广播冲突严重,调整中继间隔后稳定运行。

小Tips:Mesh开发初期,建议用少量节点(3-5个)测试网络行为。nRF Mesh App的日志功能能帮你分析消息转发路径。

http://www.lryc.cn/news/615960.html

相关文章:

  • Spring Boot 全局异常处理与日志监控实战
  • go加速配置(下载第三方库)
  • 元数据管理与数据治理平台:Apache Atlas 通知和业务元数据 Notifications And Business Metadata
  • 《Go小技巧易错点100例》第三十七篇
  • 元数据管理与数据治理平台:Apache Atlas 分类传播 Classification Propagation
  • SQL(结构化查询语言)的四大核心分类
  • 【机器学习深度学习】Embedding 模型详解:从基础原理到实际应用场景
  • MySQL 处理重复数据详细说明
  • 【软件测试】性能测试 —— 工具篇 JMeter 介绍与使用
  • 联合理解生成的关键拼图?腾讯发布X-Omni:强化学习让离散自回归生成方法重焕生机,轻松渲染长文本图像
  • 如何部署图床系统 完整教程
  • ESP32安装于配置
  • Oracle 19C 查看卡慢的解决思路
  • AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年8月10日第159弹
  • Spring Boot 注解详解:@RequestMapping 的多种用法
  • 第4章 程序段的反复执行4 多重循环练习(题及答案)
  • RAGFlow 拉取 Docker 镜像失败
  • 压力测试等工具源码包编译及使用方法
  • 基于python高校固定资产管理系统
  • 【银行测试】保险项目测试点+测试流程详情(二)
  • scanpy单细胞转录组python教程(一):不同形式数据读取
  • java报错“ NoSuchMethodError:com.test.Service.doRoomList(Ljava/lang/String;)V解决方案
  • Gin 框架错误处理机制详解
  • 线性代数1000题学习笔记
  • 如何将PDF文档进行高效编辑处理!
  • NLP学习开始-02逻辑回归
  • 【Spring IoC 核心实现类详解:DefaultListableBeanFactory】
  • 从策略梯度到 PPO
  • Linux权限管理终极指南(用户身份与文件权限
  • Python中的 __name__