BM70/71蓝牙5.0模块开发实战:从集成优势到低功耗物联网应用

1. 项目概述:为什么是BM70/71?

在嵌入式物联网的世界里,无线连接是项目的“呼吸”。几年前,为一个传感器节点或便携设备选型蓝牙模块,你可能得在功耗、成本、开发难度和性能之间反复权衡,最后往往只能妥协。但当我第一次接触到Microchip的BM70和BM71系列蓝牙5.0低功耗模块时,感觉就像找到了一个“六边形战士”——它几乎在各个方面都达到了一个巧妙的平衡点。

简单来说,BM70和BM71是Microchip推出的两款高度集成的蓝牙5.0低功耗模块。它们不仅仅是简单的射频收发器,而是内置了完整的蓝牙协议栈、应用处理器和丰富外设的片上系统。这意味着,你可以把它当作一个独立的微控制器来编程,实现复杂的逻辑控制,同时它又天生具备稳定、低功耗的蓝牙连接能力。对于开发者而言,这极大地简化了系统设计:你不再需要一个主MCU通过UART去“指挥”一个从属的蓝牙模块,而是可以直接在模块上跑你的应用代码,实现真正的单芯片无线解决方案。

这两款模块的核心差异在于内存和GPIO数量,以适应不同复杂度的应用。BM70通常配置了128KB的Flash和24KB的RAM,而BM71则提供了更大的存储空间,例如256KB Flash和64KB RAM,并拥有更多的GPIO引脚。这使得BM71能够胜任更复杂的应用,比如需要OTA升级、存储更多数据或连接更多传感器的场景。无论是智能家居的传感器、医疗可穿戴设备、资产追踪标签,还是工业遥控器,这个系列模块都能提供可靠的连接基石。

我选择深入折腾这个模块,是因为在实际项目中,我们经常遇到这样的困境:主控芯片资源紧张,加上外挂蓝牙模块后,功耗、PCB面积和软件复杂度都成倍增加。BM70/71这种All-in-One的方案,恰恰击中了这些痛点。接下来,我就把自己从选型评估、环境搭建、到实际开发调试的全过程经验,毫无保留地分享出来。

2. 核心优势与选型决策

在决定采用BM70/71之前,我对比过市面上好几类常见的蓝牙方案。比如,使用Nordic或TI的芯片自行设计射频电路,优点是极致灵活和成本可控,但对射频设计和天线匹配的要求极高,量产一致性是个大挑战。另一种是使用简单的“透传模块”,通过AT指令集控制,上手快但功能受限,无法实现复杂的自定义服务和低功耗策略。

BM70/71的方案站在了中间一个非常舒服的位置。它的核心优势,我总结为以下四点:

2.1 高度集成,降低开发门槛模块已经通过了蓝牙 SIG 认证,集成了晶体、射频匹配电路和板载天线(或陶瓷天线接口)。这意味着你几乎不需要关心复杂的射频硬件设计,只需要像使用一颗普通的MCU一样,处理好电源和几个必要的IO口即可。这对于硬件团队来说,节省了大量的调试时间和认证成本。

2.2 双核架构与灵活功耗管理模块内部基于Microchip的PIC单片机核心,并集成一个独立的蓝牙低功耗控制器。这种架构允许应用代码和蓝牙协议栈高效协同。在功耗管理上,它支持多种低功耗模式,特别是“深度睡眠”模式,电流可以低至几百纳安级别。你可以通过编程,让设备在绝大部分时间处于深度睡眠,仅由定时器或外部中断唤醒进行数据采集和短促的广播/连接,这对于电池供电的物联网设备至关重要。

2.3 丰富的开发资源与工具链Microchip提供了相对完整的开发生态系统。MPLAB X IDE是免费的集成开发环境,配合其专用的编译器,可以进行C语言编程。更重要的是,Microchip提供了基于其“Harmony”框架的蓝牙协议栈和应用示例。虽然Harmony框架有一定学习曲线,但它提供了结构清晰、可移植性强的代码基础,特别是对于处理蓝牙连接、事件和电源管理这些复杂任务,能避免很多底层陷阱。

2.4 蓝牙5.0特性加持支持蓝牙5.0意味着拥有了2M PHY(高速率)、长距离模式、以及更强大的广播能力。对于需要更快数据吞吐量(如固件升级)或更远传输距离(如室内定位信标)的应用,这些特性是刚需。BM70/71对这些特性有良好的支持。

在BM70和BM71之间做选择,主要看你的应用复杂度:

  • 选择BM70:如果你的应用逻辑相对简单,例如一个周期性上报温度的传感器,不需要存储大量历史数据,且GPIO需求在10个以内,BM70是性价比更高的选择。
  • 选择BM71:如果你的设备需要实现复杂的用户交互(多按键、LED指示)、本地数据记录、通过蓝牙进行无线固件升级,或者需要连接多个数字/模拟传感器,那么BM71更大的内存和更多的IO将是必须的。

注意:模块的Flash不仅存储你的应用程序,还要存储蓝牙协议栈和配置文件。务必在项目初期就评估好代码和数据的体积,预留足够的余量(建议至少30%)以应对后续功能增加和OTA升级的需求。

3. 开发环境搭建与第一个项目

拿到模块后,第一步不是急着写代码,而是把开发环境理顺。这一步走稳了,后面的开发效率会高很多。

3.1 硬件准备你需要以下几样东西:

  1. BM70/71评估板或自制核心板:强烈建议先从官方评估板入手,比如BM71-EK评估套件。它集成了编程调试器、按钮、LED和丰富的扩展接口,能让你快速验证功能。
  2. 编程调试器:Microchip的PICKit 4或Snap是官方推荐的工具。它们通过ICSP接口与模块连接,用于烧录程序和调试。
  3. 一台蓝牙测试设备:可以是手机(安装一个通用的BLE扫描工具,如nRF Connect)或者另一个蓝牙开发板。

3.2 软件安装

  1. 安装MPLAB X IDE:从Microchip官网下载最新版本。安装时,记得勾选对应的编译器(XC8 for PIC)。
  2. 安装Harmony框架:Microchip提供了一个名为“MPLAB Harmony Configurator (MHC)”的插件和框架内容。你可以通过MPLAB X IDE内的插件中心或独立安装包来安装。Harmony 3是当前的主流版本,它采用图形化配置工具来生成初始化代码,大大简化了外设和中间件(包括蓝牙)的配置过程。
  3. 获取设备支持包和示例代码:在MPLAB X的包管理器中,确保安装了对应PIC器件的支持包。同时,去Microchip官网搜索BM70/71,下载最新的应用笔记和示例项目,这是最好的学习起点。

3.3 创建第一个“Hello BLE”项目我们以创建一个最简单的、广播设备名称并允许连接的BLE设备为例。

  1. 新建Harmony项目:在MPLAB X中,选择“File -> New Project”,选择“32-bit MPLAB Harmony Project”。在器件选择中,根据你的模块具体型号选择(例如PIC32CX-BZ系列)。
  2. 图形化配置
    • 在项目打开后,MPLAB X会自动启动Harmony Configurator。
    • 在“Project Graph”中,从“Available Components”列表里添加“BLE”组件。
    • 配置BLE组件:设置一个易识别的设备名称(如MyBM71_Device)。在“GAP”角色中,选择“Peripheral”(外围设备)。系统会自动生成一个基本的Generic Access (GAP) 和 Generic Attribute (GATT) 服务。
    • 你可以添加一个自定义服务。例如,添加一个“Environmental Sensing”服务,并在其下添加一个“Temperature”特征。将这个特征的属性设置为“Read”和“Notify”,这样手机就可以读取并订阅温度通知。
    • 配置时钟、引脚(用于连接LED和按钮)等系统外设。
  3. 生成代码:点击“Generate Code”,Harmony会根据你的图形配置,生成所有底层的驱动、蓝牙协议栈交互代码以及一个清晰的应用骨架。
  4. 编写应用逻辑:在生成的项目中,你的主要工作集中在app.capp.h文件中。这里有一个主任务循环和一系列由框架调用的回调函数。
    • APP_Initialize函数中,初始化你的应用状态。
    • APP_Tasks函数的主循环中,你可以检查事件标志,执行你的主要逻辑,例如读取传感器数据。
    • 最关键的是处理蓝牙事件。框架会通过回调函数(如APP_BleGapEvent_T)通知你蓝牙连接、断开、数据写入等事件。你需要在这里编写代码来响应这些事件。例如,当手机订阅了温度特征的通知后,你可以在传感器数据更新时,调用BLE栈提供的函数主动发送通知数据给手机。
// 示例:在蓝牙事件回调中处理特征订阅 void APP_BleGattEventCallback(uint16_t eventType, void *eventData) { switch(eventType) { case BT_GATTS_EVENT_WRITE: // 处理手机写入的数据(例如,控制指令) break; case BT_GATTS_EVENT_CCCD_WRITE: // 处理客户端特征配置描述符写入(即订阅/取消订阅通知) gattEventData = (BT_GATTS_EVENT_DATA *)eventData; if (gattEventData->eventField.cccdWrite.handle == temperatureCharHandle) { if (gattEventData->eventField.cccdWrite.cccdValue & BT_GATT_CCCD_NOTIFICATION) { isTemperatureNotifyEnabled = true; // 标记通知已开启 } else { isTemperatureNotifyEnabled = false; // 标记通知已关闭 } } break; } }
  1. 编译与烧录:连接好PICKit 4和评估板,在MPLAB X中点击“Clean and Build”,然后点击“Make and Program Device”。如果一切顺利,程序将被烧录到模块中。
  2. 测试:给模块上电,打开手机上的BLE扫描工具(如nRF Connect),你应该能看到名为MyBM71_Device的设备在广播。点击连接,可以浏览到它的服务列表,包括你自定义的温度服务。尝试读取特征值,或者启用通知。

实操心得:Harmony框架生成的代码结构比较庞大,初次接触可能会感到困惑。不要试图立刻理解所有文件,重点关注app.c和与蓝牙事件相关的回调函数。把官方示例项目导入,并实际运行起来,通过修改示例来学习,比从头开始阅读文档要高效得多。

4. 低功耗设计与优化实战

对于物联网设备,功耗直接决定了产品的续航和用户体验。BM70/71的功耗优化,是开发中的重中之重。低功耗不是简单地调用一个“睡眠”函数,而是一整套系统性的设计策略。

4.1 理解功耗模式BM70/71通常支持多种运行模式,例如:

  • 运行模式:CPU和蓝牙射频全速工作,功耗最高(几十mA级)。
  • 空闲模式:CPU暂停,但外设和蓝牙协议栈可能仍在运行,功耗中等。
  • 睡眠模式:大部分时钟关闭,仅部分外设(如RTC、看门狗)和SRAM保持供电,蓝牙射频关闭,功耗在微安级。
  • 深度睡眠模式:功耗最低的模式(可达几百纳安),仅极少数唤醒源(如外部中断、特定定时器)能唤醒系统。此时SRAM内容可能丢失(取决于具体型号和配置)。

4.2 设计低功耗应用流程一个典型的数据采集+上报的物联网节点,其功耗优化流程如下:

  1. 初始化与快速启动:设备上电后,以最快速度完成硬件初始化、读取配置、连接传感器。
  2. 数据采集与处理:启动ADC读取传感器数据,进行必要的滤波或计算。此阶段应高效完成,避免不必要的延时循环。
  3. 无线活动期
    • 如果是广播设备:在配置好的广播间隔内,快速开启射频,发送广播包,然后立即关闭射频进入睡眠。
    • 如果是连接设备:与手机或网关建立连接。在连接间隔内,设备只在约定的“连接事件”窗口醒来,与中心设备交换数据。交换完毕后,立即进入睡眠,直到下一个连接事件。连接间隔是功耗的关键,间隔越长,平均功耗越低,但数据实时性越差。需要在两者间权衡。
  4. 进入低功耗模式:所有任务完成后,明确地让系统进入预设的低功耗模式(睡眠或深度睡眠)。在Harmony框架中,这通常通过调用SYS_DEVCON_PowerModeEnter()之类的函数实现。
  5. 由中断唤醒:配置一个硬件定时器(RTC/Timer)作为唤醒源。当定时器超时,产生中断,将系统从睡眠中唤醒,然后跳转到步骤1或2,开始新的工作周期。

4.3 关键代码实现与配置在Harmony项目中,你需要做以下配置和编码:

  • 配置低功耗管理器:在Harmony Configurator中,确保“Power Manager”组件被正确添加和配置,支持你计划使用的睡眠模式。
  • 配置唤醒源:在定时器或RTC组件的配置中,启用其作为低功耗模式下的唤醒源。
  • 编写应用状态机:在你的APP_Tasks中,实现一个清晰的状态机。例如:
    typedef enum { APP_STATE_INIT, APP_STATE_SAMPLE_SENSOR, APP_STATE_BLE_PROCESS, APP_STATE_IDLE, APP_STATE_ENTER_SLEEP } APP_STATES; void APP_Tasks(void) { switch(appState) { case APP_STATE_INIT: // 初始化硬件 SENSOR_Init(); appState = APP_STATE_SAMPLE_SENSOR; break; case APP_STATE_SAMPLE_SENSOR: sensorValue = SENSOR_Read(); appState = APP_STATE_BLE_PROCESS; break; case APP_STATE_BLE_PROCESS: if (isBleConnected && isNotifyEnabled) { BLE_SendNotification(sensorValue); // 发送数据 } appState = APP_STATE_IDLE; break; case APP_STATE_IDLE: // 检查是否有任务需要立即处理,如果没有,准备睡眠 if (/* 无紧急任务 */) { appState = APP_STATE_ENTER_SLEEP; } break; case APP_STATE_ENTER_SLEEP: // 保存必要状态(如果需要) // 关闭不需要的外设(如ADC、LED驱动) // 调用进入睡眠的函数 POWER_EnterSleepMode(); // 执行此函数后,CPU暂停。唤醒后,将从中断服务程序跳转回来,通常需要重置状态机到初始或某个状态。 appState = APP_STATE_INIT; // 或根据唤醒源决定 break; } }
  • 处理唤醒中断:在唤醒源的中断服务程序中,通常不需要做复杂操作,只需清除中断标志。系统退出睡眠模式后,会继续执行APP_Tasks中的代码。

4.4 实测与优化技巧

  • 使用电流表测量:必须用高精度电流表或带有电流测量功能的电源(如Joulescope)来实际测量不同状态下的电流。这是验证功耗优化效果的唯一标准。
  • 优化广播/连接参数:在蓝牙配置中,增加广播间隔和连接间隔能显著降低功耗,但会影响设备被发现的速度和数据实时性。根据应用场景找到最佳平衡点。
  • 关闭无用外设和引脚:在进入睡眠前,通过代码将未使用的GPIO设置为输出低电平或带上拉输入,避免引脚悬空产生漏电流。关闭所有不用的外设模块时钟。
  • 减少活动时间:优化你的应用代码,让CPU在采集、处理、发送数据时以最高效的速度运行,然后尽快进入睡眠。避免使用delay()这类忙等待函数。

注意事项:深度睡眠模式下,SRAM数据可能丢失。如果你的应用需要在睡眠期间保存某些变量,需要确认模块是否支持“保持SRAM”的睡眠模式,或者将关键数据存入Flash(需注意Flash写操作功耗高、寿命有限)。另一种常见做法是,每次唤醒都从传感器或外部EEPROM中重新读取数据。

5. 蓝牙协议栈深入与自定义服务开发

当你掌握了基础的数据收发后,往往需要实现更复杂的交互逻辑,这就需要对蓝牙协议栈,特别是GATT层有更深的理解。BM70/71的Harmony蓝牙协议栈提供了清晰的API,但需要遵循其事件驱动的编程模型。

5.1 GATT服务与特征设计GATT是蓝牙设备交换数据的结构化方式。一个设备包含若干服务,每个服务包含若干特征,每个特征包含一个值和若干描述符。

  • 服务:代表一个独立的功能单元,如电池服务、设备信息服务、自定义的数据服务。
  • 特征:服务中的数据点。它有一个唯一的UUID、一个数值、以及一组属性(如可读、可写、可通知)。
  • 描述符:描述特征的元数据,最重要的就是客户端特征配置描述符,用于启用或禁用通知/指示。

在Harmony Configurator中添加自定义服务非常直观。你可以使用标准的16位UUID(由蓝牙技术联盟定义),也可以使用自定义的128位UUID以确保唯一性。对于大多数私有设备,使用128位UUID是常见做法。

5.2 实现双向数据通信一个完整的物联网节点通常需要支持两种数据流:

  1. 上行(设备->手机):通过特征的“通知”属性实现。设备在数据更新时,主动推送数据给已订阅的手机。这是最节能的上行方式,因为手机不需要频繁轮询。
  2. 下行(手机->设备):通过特征的“写”或“写(无响应)”属性实现。手机向特征写入数据,设备在GATT事件回调中接收并处理这些数据,例如接收控制命令、配置参数。

5.3 处理长数据与OTA升级当需要传输的数据包超过蓝牙单次传输的MTU(通常默认是23字节,可通过协商扩大)时,就需要在应用层进行分包和组包。Microchip的协议栈通常提供了数据分发的机制,但逻辑需要自己实现。

更复杂的场景是无线固件升级。BM70/71支持通过蓝牙进行OTA DFU。这需要:

  1. 在应用程序中实现一个独立的DFU服务。
  2. 设备进入DFU模式,接收手机端发送的新固件二进制数据包,并写入到Flash的特定区域。
  3. 校验固件完整性,并跳转到新固件运行。 Microchip通常会提供DFU的库和参考实现,但这部分开发复杂度较高,需要仔细处理Flash操作、断电保护和启动引导程序。

5.4 连接参数更新与链路监控建立连接后,外围设备可以主动向中心设备发起“连接参数更新请求”,以协商更合适的连接间隔、延迟等参数,从而优化功耗或吞吐量。同时,你的应用程序需要监控连接状态,处理意外断开并尝试重连,或者进入广播状态等待重新连接。

// 示例:在连接建立后,请求更长的连接间隔以降低功耗 void requestLowerPowerConnection(void) { BT_GAP_CONN_PARAM connParam; connParam.intervalMin = 160; // 最小连接间隔 = 160 * 1.25ms = 200ms connParam.intervalMax = 320; // 最大连接间隔 = 320 * 1.25ms = 400ms connParam.latency = 4; // 从机延迟 connParam.timeout = 600; // 监控超时 = 600 * 10ms = 6s BT_GAP_UpdateConnParams(connectionHandle, &connParam); }

6. 硬件设计要点与射频性能保障

虽然BM70/71是模块,极大简化了射频设计,但要在产品中稳定可靠地工作,硬件设计上仍有几个雷区必须避开。

6.1 电源设计蓝牙射频在发射时会有瞬间的电流峰值(可能达到十几mA甚至更高)。电源必须能提供稳定、干净的电压,并具有足够的瞬态响应能力。

  • 使用LDO或DC-DC:推荐使用高性能、低噪声的LDO为模块供电。确保LDO的最大输出电流远大于模块的峰值电流需求。
  • 充分去耦:在模块的VCC引脚附近(1cm以内)放置一个10μF的钽电容或陶瓷电容,并并联一个0.1μF的陶瓷电容。这是吸收低频和高频噪声的关键,布局上电容的接地回路要尽可能短。
  • 避免数字噪声:如果系统中还有电机、继电器等大电流开关器件,务必做好电源隔离和滤波,防止噪声耦合到模块电源,导致射频性能下降甚至通信中断。

6.2 天线与射频布局

  • 天线选择:如果模块自带板载天线,务必严格按照数据手册的布局要求,在天线区域下方和周围进行净空处理(禁止铺铜和走线)。如果使用外接天线接口,选择匹配频率的陶瓷天线或外置天线,并使用特性阻抗为50欧姆的微带线进行连接。
  • 晶振:模块依赖外部晶振提供时钟基准。晶振及其负载电容应尽可能靠近模块的晶振引脚布局,走线短且对称,下方用接地铜皮屏蔽。

6.3 GPIO与外围电路

  • 未用引脚处理:将所有未使用的GPIO在软件中配置为确定的输出状态(高或低),或者使能内部上拉/下拉,绝对避免浮空。
  • 唤醒引脚:如果使用外部中断引脚(如按键)作为深度睡眠唤醒源,建议在引脚处增加一个对地的电容(如0.1μF)以滤除抖动,并根据需要配置内部上拉电阻。

6.4 PCB设计检查清单

检查项要求潜在问题
电源走线宽度足够宽,满足电流需求压降过大,射频发射时电压跌落
电源去耦电容紧贴模块VCC引脚去耦效果差,系统不稳定
天线区域严格净空,无任何走线/铜皮天线性能严重恶化,通信距离骤减
晶振布局靠近芯片,走线短,对称时钟不准,导致蓝牙频率偏移,连接失败
数字信号线远离射频路径和天线数字噪声干扰射频接收灵敏度

7. 调试技巧与常见问题排查

开发过程中,遇到问题是常态。一套高效的调试方法能让你快速定位问题所在。

7.1 软件调试工具

  • MPLAB X IDE调试器:结合PICKit 4,可以进行单步调试、设置断点、查看变量和内存。这是解决逻辑错误的最强武器。务必学会使用。
  • 串口打印:在代码关键位置通过UART输出调试信息。BM70/71通常有富余的UART引脚。这是一种古老但极其有效的方法。可以将调试信息输出到PC端的串口助手。
  • LED指示:用不同的LED闪烁模式来表示不同的程序状态(如初始化成功、正在广播、已连接、发生错误)。当没有调试器时,这是最直观的状态诊断方法。

7.2 蓝牙协议层调试

  • 手机端BLE调试App:nRF Connect、LightBlue等。它们可以显示设备的广播数据、服务列表、特征值,并能进行读写、订阅操作。这是验证你的GATT数据库配置是否正确的最直接工具。
  • 空中数据包嗅探器:如Ellisys、Frontline等专业蓝牙嗅探器。它们可以捕获和分析空中传输的每一个蓝牙数据包,是解决复杂的连接、断线、数据错误问题的终极工具。当然,这类设备价格昂贵。

7.3 常见问题速查表

现象可能原因排查步骤
手机搜不到设备1. 模块未供电或复位。
2. 程序未运行或卡死。
3. 广播未开启或广播参数设置异常。
4. 射频电路故障(天线问题)。
1. 检查电源电压和电流。用示波器看复位引脚。
2. 连接调试器,看程序是否运行到蓝牙初始化代码。
3. 确认BT_GAP_SetAdvEnable被调用。检查广播间隔是否太短(如小于20ms)或太长。
4. 检查天线匹配电路和布局。
可以搜索但无法连接1. 设备已处于连接状态。
2. 白名单过滤。
3. 协议栈内部错误。
1. 用手机App检查设备是否已被其他设备连接。
2. 检查代码中是否启用了白名单且未包含你的手机地址。
3. 查看调试信息或增加错误处理回调。
连接后立即断开1. 连接参数不兼容。
2. 信号强度太弱(RSSI过低)。
3. 设备端资源不足(如内存分配失败)。
1. 尝试在手机端或设备端调整连接参数(间隔、延迟)。
2. 拉近设备与手机距离,或检查天线性能。
3. 检查协议栈初始化是否成功,内存池是否耗尽。
数据收发错误或丢失1. MTU大小不足,长数据未分包。
2. 通知未启用就发送数据。
3. 应用层处理速度慢,数据缓冲区溢出。
4. 电源噪声导致通信错误。
1. 在连接后协商更大的MTU。在应用层实现分包逻辑。
2. 确保手机已写入CCCD启用通知后,再调用发送函数。
3. 优化代码,确保收到数据后能快速处理。增加流控机制。
4. 用示波器检查电源纹波,加强去耦。
功耗远高于预期1. 未进入低功耗模式。
2. 唤醒源配置错误,频繁唤醒。
3. 外设未关闭。
4. GPIO引脚悬空。
1. 确认调用了进入睡眠的函数,并用电流表测量睡眠电流。
2. 检查定时器中断是否意外频繁触发。
3. 在睡眠前,在代码中关闭ADC、UART等外设时钟。
4. 配置所有未使用引脚为输出低或带上拉输入。

7.4 程序“跑飞”或Hard Fault这是最令人头疼的问题之一。除了常规的数组越界、空指针访问,在蓝牙开发中还需特别注意:

  • 栈溢出:蓝牙协议栈事件回调、中断服务程序都会消耗栈空间。如果应用层任务栈设置太小,可能导致栈溢出并破坏内存。在MPLAB X的工程属性中,适当增大栈和堆的大小。
  • 中断冲突:确保蓝牙协议栈使用的中断优先级和你的应用中断优先级配置正确,避免在关键段被意外打断。
  • 看门狗:启用看门狗定时器,并在主循环中定期喂狗。当程序跑飞时,看门狗能复位系统,让设备恢复工作,而不是死机。

调试是一个系统性工程,从电源、信号到软件逻辑,需要耐心地逐层排除。养成“假设-验证-修改”的科学调试习惯,并善用工具记录每次实验现象,能极大提升效率。