1. 项目概述:为什么选择Espruino来造一个滑板遥控器?

几年前,当我第一次尝试给我的长板加装电驱套件时,最头疼的不是电机和电池,而是那个手感廉价、功能单一的成品遥控器。它要么延迟高得吓人,要么续航短得可怜,最关键的是,它完全“不属于”我的滑板。作为一个喜欢折腾的开发者,我萌生了自己动手做一个的念头。市面上主流的方案是Arduino搭配C/C++,或者是ESP32搭配MicroPython,但我最终选择了 Espruino ——一个允许你用 JavaScript 来编写嵌入式固件的微控制器平台。这个决定让整个开发过程变得异常“友好”。

你可能好奇,为什么是JavaScript?在嵌入式领域,C语言是当之无愧的王者,性能极致,但对新手和快速迭代来说,门槛不低。Espruino的核心价值在于,它将Web开发中那套熟悉的、基于事件驱动的编程模型带到了硬件层面。你不需要纠结于内存管理、指针这些底层细节,可以更专注于业务逻辑:比如“当摇杆被推动时,通过无线电发送一个油门值”。这对于需要快速验证想法、注重开发效率的DIY项目来说,简直是神器。本次要做的电动滑板遥控器,就是一个绝佳的实践案例:它需要读取传感器(霍尔传感器或摇杆)、驱动显示屏、管理电池电量,并通过无线模块与滑板上的接收器通信。用Espruino,你可以像写一个网页交互脚本一样,把这些功能串联起来。

这个项目适合谁呢?首先是有一定动手能力的创客或电子爱好者,你想深入理解无线遥控系统是如何工作的。其次是前端或全栈开发者,你想跨出舒适区,用自己熟悉的JavaScript语言触碰物理世界,这会是一次极其有趣的体验。当然,它也适合那些对成品遥控器不满意,渴望拥有一个完全自定义、从外观到交互都由自己掌控的滑板玩家。整个项目会涉及硬件选型、焊接组装、电路设计基础以及Espruino编程,我会把每个环节的“为什么”和“怎么做”都掰开揉碎讲清楚,尤其是那些官方文档里不会提的坑和技巧。

2. 核心硬件选型与设计思路解析

自己造遥控器,硬件是骨架。选型不是堆砌最贵的部件,而是在成本、可靠性、易用性和项目需求之间找到最佳平衡点。我的设计目标是: 低延迟、高可靠性、长续航、可编程性强 。下面我们来拆解每一个关键部件背后的考量。

2.1 微控制器:为何是Espruino MDBT42Q?

遥控器的大脑,我选择了 Espruino MDBT42Q 。市面上可选的主控很多,比如经典的Arduino Nano(ATmega328P),或者功能更强的ESP32。选择MDBT42Q,主要是基于以下几点综合判断:

  1. 内置蓝牙与JavaScript引擎 :MDBT42Q是一块基于nRF52832芯片的板子,它集成了低功耗蓝牙(BLE)射频模块。这意味着无线通信功能是原生的,无需额外模块,简化了设计和布线。更重要的是,它预装了Espruino固件,开机即是一个JavaScript解释器。
  2. 开发效率与调试便利性 :用JavaScript开发,你可以通过USB连接电脑,在Web IDE或命令行里实时执行代码、查看变量、调试逻辑,就像在浏览器控制台里调试网页一样。这种即时反馈对于调试传感器读数、无线数据包等动态过程至关重要,远比“编写-编译-上传-重启-查看”的传统嵌入式流程高效。
  3. 充足的IO与性能 :nRF52832拥有足够的GPIO引脚来连接屏幕、传感器、开关,其ARM Cortex-M4内核的性能足以流畅处理本项目的逻辑、驱动OLED以及运行BLE协议栈,不会有性能瓶颈。

注意 :Espruino官方也有其他板型,如Puck.js、Espruino WiFi等。选择MDBT42Q是因为它引脚引出完整,便于在面包板或洞洞板上搭建原型,且价格相对适中。如果你手头有ESP32开发板,也可以刷入Espruino固件来替代,但初次配置会稍麻烦一些。

2.2 油门信号输入:霍尔传感器 vs. 游戏摇杆

这是遥控器的核心输入设备,决定了油门控制的精度和手感。原文提到了两种方案: 线性比率式霍尔效应传感器 (如DRV5055)和 游戏摇杆

  • 线性霍尔传感器(推荐方案)

    • 原理 :传感器输出一个与所处磁场强度成正比的模拟电压。我们将一块小磁铁固定在遥控器的滑块或扳机上,当用户推动时,磁铁靠近或远离传感器,引起磁场变化,从而输出变化的电压。控制器读取这个电压值,就能精确得知油门位置。
    • 优势 无接触、无磨损、寿命极长、精度高、手感平滑线性 。这是商用高端遥控器的常见方案,能提供非常跟手的控制体验。
    • 选型要点 :DRV5055A2QLPG是一款常见的3.3V供电、模拟量输出的霍尔传感器。购买时务必注意其灵敏度(mV/mT)和供电电压范围是否与你的系统(3.3V)匹配。同时需要准备一块尺寸合适的钕铁硼磁铁。
  • 游戏摇杆(备选/快速原型方案)

    • 原理 :通常是两个电位器(分别对应X/Y轴)和一个按键。我们只使用其中一个轴(如Y轴)作为油门信号。
    • 优势 极易获取、接线简单(通常只有VCC, GND, VRX, VRY, SW五个引脚)、成本低廉 。适合快速验证想法或预算极其有限的情况。
    • 劣势 :电位器属于机械接触式元件,存在物理磨损,长期使用后可能出现跳动、噪音或线性度变差的问题。手感上也通常不如精心调校的霍尔方案。

我的建议与最终选择 :为了项目的长期可靠性和专业度, 强烈推荐使用霍尔传感器方案 。虽然初期需要设计一个机械结构来固定磁铁和传感器(比如3D打印一个滑块导轨),但这部分投入是值得的。在本项目中,我将以DRV5055为例进行详细讲解。如果你暂时用摇杆入门,也完全可行,大部分代码逻辑是通用的,只是读取的引脚和数值范围需要调整。

2.3 人机交互:OLED显示屏与按键

遥控器需要给用户反馈,最基本的就是显示当前状态。

  • OLED显示屏(SSD1306, 128x32) :选择I2C接口的0.96英寸OLED屏,原因如下:

    1. 省电 :OLED是自发光,显示深色像素时几乎不耗电,非常适合电池设备。
    2. 高对比度 :即使在阳光下也有不错的可视性。
    3. 接口简单 :I2C总线只需连接SDA(数据)、SCL(时钟)、VCC(3.3V)、GND四根线,节省IO口。
    4. 分辨率够用 :128x32像素足以显示速度、电量、连接状态、模式等关键信息。
  • 按键 :原文建议至少一个轻触开关和一个拨动开关。

    • 轻触开关 :用作“功能键”,例如短按切换显示页面,长按开关机或进入配对模式。
    • 拨动开关 :用作“硬件开关”,最安全的用途是控制整个遥控器的电源通断。即使用软件实现了关机,一个物理断电开关也能彻底杜绝待机耗电,避免下次想玩时电池已耗尽。

2.4 供电系统:电池管理与安全

遥控器需要移动供电,安全、高效是首要原则。

  • 电池 :选用单节 18650锂离子电池 。其理由很充分:能量密度高、放电能力强、规格统一易获取、有成熟的充电和保护方案。容量建议选择2600mAh以上,以确保数周的续航。

  • 充电/保护板 :这是 绝对不能省略 的部分!你需要一块集成了TP4056充电芯片和DW01A保护芯片的小板子。它的作用是:

    1. 充电管理 :提供稳定的5V转4.2V充电流程,支持恒流恒压充电。
    2. 过充/过放保护 :当电池电压高于4.25V或低于2.5V左右时,自动切断电路,防止电池损坏甚至发生危险。
    3. 短路保护
    • 接线 :电池的正负极焊接到保护板的B+和B-;保护板的输出端(OUT+和OUT-)则作为整个遥控器系统的电源正负极。
  • 电压检测电路(关键细节) :为了在OLED上显示电池电量,我们需要测量电池电压。但Espruino的模拟输入引脚只能承受最大3.3V电压,而18650电池满电电压约4.2V。直接连接会烧毁芯片!因此必须使用一个 电压分压电路 。通常用两个电阻串联,例如一个100kΩ(R1)和一个47kΩ(R2)的电阻。电池电压接在串联电阻两端,从两个电阻中间连接ADC引脚。这样,ADC读取的电压 V_adc = V_bat * (R2 / (R1 + R2)) 。计算一下: 4.2V * (47k / (100k+47k)) ≈ 1.34V ,安全地落在3.3V以内。在代码中,我们再通过公式 V_bat = V_adc * ((R1+R2) / R2) 反算出真实电池电压。

3. 电路连接与硬件组装实操指南

理论清楚了,现在开始动手。这一步需要耐心和细心,良好的硬件基础是稳定运行的保障。

3.1 绘制连接示意图(虽无原理图,但心中有图)

虽然原文没有提供原理图,但我们可以根据功能模块梳理出清晰的接线表。假设我们使用霍尔传感器方案。

元件 引脚 连接到 Espruino MDBT42Q 引脚 说明
OLED (SSD1306) VCC 3.3V 电源正极
GND GND 电源地
SDA P0.30 (SDA) I2C数据线
SCL P0.31 (SCL) I2C时钟线
霍尔传感器 (DRV5055) VCC 3.3V 电源正极
GND GND 电源地
VOUT P0.02 (A0) 模拟信号输出,接ADC引脚
拨动开关 一端 电池保护板OUT+ 接入主电源正极
另一端 整个系统VCC总线 控制总电源通断
轻触开关 一端 P0.15 (配置内部上拉) 作为数字输入
另一端 GND 按下时接地,触发低电平
电压分压电路 R1 (100k)一端 电池保护板OUT+ (Vbat) 接电池正极
R1, R2连接点 P0.03 (A1) 分压后电压测量点
R2 (47k)一端 GND 接地

接线要点与实操心得

  1. 电源走线 :建议用较粗的导线(如AWG22)连接电池和保护板,以及从开关到主VCC总线。大电流路径电阻要小。
  2. 信号线 :I2C的SDA和SCL线最好并列走线,避免过长。如果遇到屏幕显示不稳定,可以尝试在SDA和SCL线上各加一个4.7kΩ的上拉电阻到3.3V(有些模块已集成)。
  3. 接地 :务必确保 所有元件的GND引脚最终都连接到同一个“地” ,即电池保护板的OUT-。混乱的接地是噪声和不稳定性的主要来源。建议在洞洞板或PCB上布置一条粗壮的“地线总线”。
  4. 焊接 :确保焊点圆润光滑,无虚焊、短路。焊接Espruino引脚时,电烙铁温度不宜过高(350°C左右),并确保接地良好,防止静电击穿芯片。

3.2 结构组装与固定技巧

硬件电路不能“飞线”裸奔,需要一个外壳来保护和组织。

  1. 外壳设计与获取 :你可以使用现成的塑料项目盒子钻孔改造,也可以像我一样进行 3D打印 。设计外壳时需要考虑:

    • 所有元件的定位孔(屏幕、开关、USB充电口)。
    • 霍尔传感器的固定位置以及磁铁滑块的滑动轨道。这个轨道需要光滑,阻力适中,可以用一小段铝型材或精心设计的塑料轨道。
    • 电池仓的形状和固定方式。
    • 上下盖的固定方式(螺丝柱或卡扣)。
    • 可以在Thingiverse等网站搜索“electric skateboard remote”找现成模型修改,这是最快捷的方式。
  2. 元件固定

    • 屏幕 :不建议直接用热熔胶,夏天易软化。可以使用少量 环氧树脂胶 双面泡棉胶 ,后者有一定缓冲作用。
    • 主控板与电池 :在壳体内壁设计卡槽或使用尼龙扎带固定。电池务必用绝缘胶带包裹好电极后再固定,防止短路。
    • 开关与传感器 :确保开关拨动顺畅,传感器位置精准。霍尔传感器应使用胶水牢牢固定在其安装座上。
  3. 总装 :先将所有元件在外壳内大致摆位,理清线材。连接好所有导线后,仔细检查一遍再通电测试。最后合上盖子,拧紧螺丝。一个专属于你的遥控器硬件部分就诞生了。

4. Espruino固件开发详解

硬件准备就绪,现在注入灵魂。我们将用JavaScript编写遥控器的所有逻辑。请先确保你的电脑已安装Espruino Web IDE或配置好命令行工具。

4.1 开发环境搭建与基础代码结构

首先,通过USB线将MDBT42Q连接电脑。打开Espruino Web IDE,选择正确的串口,点击连接。连接成功后,左侧控制台会显示“>`”提示符。

一个健壮的遥控器固件应该采用模块化、事件驱动的结构。下面是一个基础框架:

// ====== 硬件引脚定义 ======
var PIN = {
  hallSensor: A0,    // 霍尔传感器模拟输入
  batVoltage: A1,    // 电池电压检测(接分压中点)
  btnFunction: D15,  // 功能按键 (P0.15)
  led: LED1,         // 板载LED,用于指示
};

// ====== 全局变量与状态 ======
var state = {
  throttle: 0,       // 当前油门值,范围 -1000 到 1000 (或 0-1000)
  batteryPercent: 100,
  isConnected: false,
  displayPage: 0,    // 当前显示页面索引
};

// ====== 初始化函数 ======
function initHardware() {
  console.log("Initializing hardware...");

  // 1. 配置功能按键(内部上拉,按下为低电平)
  pinMode(PIN.btnFunction, "input_pullup");
  setWatch(function(e) {
    onButtonPressed(e);
  }, PIN.btnFunction, { repeat: true, edge: "falling", debounce: 50 });

  // 2. 初始化I2C并连接OLED屏幕
  I2C1.setup({ scl: D31, sda: D30, bitrate: 400000 });
  var g = require("SSD1306").connect(I2C1, function() {
    console.log("OLED Ready");
    g.clear();
    g.drawString("Remote Booting...", 0, 0);
    g.flip();
  });
  global.G = g; // 将图形对象设为全局,方便调用

  // 3. 初始化模拟输入(ADC)
  // Espruino默认已启用,无需额外设置

  // 4. 初始化蓝牙(BLE)
  initBluetooth();

  console.log("Hardware init done.");
}

// ====== 主循环与核心逻辑 ======
function mainLoop() {
  // 1. 读取传感器
  readThrottle();
  // 2. 读取电池电压(不需要每次循环都读,比如每10次读一次)
  // 3. 更新显示
  updateDisplay();
  // 4. 通过BLE发送数据(如果已连接)
  if (state.isConnected) {
    sendDataOverBLE();
  }
}

// 设置一个定时器,每50ms执行一次主循环(20Hz更新率)
setInterval(mainLoop, 50);

// ====== 程序入口 ======
initHardware();
console.log("Electric Skateboard Remote Firmware Started.");

代码解析与注意事项

  • setWatch :这是Espruino处理按键事件的优雅方式。它设置了一个“监视器”,当引脚电平发生指定变化( edge: "falling" 下降沿,即按下瞬间)时触发回调函数。 debounce: 50 防抖参数 ,至关重要,可以滤除按键机械抖动产生的误触发信号。
  • require("SSD1306") :这是Espruino为SSD1306驱动提供的内置模块,简化了屏幕操作。
  • 主循环频率 setInterval(mainLoop, 50) 即20Hz。对于遥控器,20-50Hz的更新率足以保证操控跟手。更高的频率会增加功耗,需要权衡。

4.2 核心功能模块实现

现在我们来填充框架中的几个核心函数。

1. 读取油门值 ( readThrottle )

var THROTTLE_DEADZONE = 50; // 死区范围,避免中间位置漂移

function readThrottle() {
  // 读取ADC原始值 (0-1.0 对应 0-3.3V)
  var analogValue = analogRead(PIN.hallSensor);

  // 根据你的传感器和磁铁安装方式,将电压映射到油门范围
  // 示例:假设静止时电压为0.5V,最大推力时电压为0.1V,最大拉力时电压为0.9V
  var neutralVoltage = 0.5;
  var range = 0.4; // 从中性点到最大值的电压变化量

  var rawThrottle = (analogValue - neutralVoltage) / range; // 范围约为 -1.0 到 1.0

  // 应用死区
  if (Math.abs(rawThrottle) < (THROTTLE_DEADZONE / 1000.0)) {
    rawThrottle = 0;
  }

  // 限制范围并转换为整数(例如 -1000 到 1000)
  rawThrottle = Math.max(-1.0, Math.min(1.0, rawThrottle));
  state.throttle = Math.round(rawThrottle * 1000);

  // 可选:添加指数曲线或平滑滤波,让操控手感更细腻
  // state.throttle = applyExpoCurve(state.throttle);
}

实操心得:校准是关键! neutralVoltage range 这两个值需要 实地校准 。在磁铁处于你定义的“中立位”时,读取 analogValue 并赋值给 neutralVoltage 。然后将磁铁推到最大位置,读取电压,计算与中立位的差值作为 range 。这个校准过程最好做一个简单的配置模式,通过按键触发并显示当前电压值到屏幕。

2. 读取电池电量 ( readBatteryVoltage )

var R1 = 100; // 单位:千欧姆
var R2 = 47;  // 单位:千欧姆
var VOLTAGE_DIVIDER_RATIO = (R1 + R2) / R2; // 分压比
var BATTERY_FULL = 4.2; // 满电电压
var BATTERY_EMPTY = 3.2; // 保护板截止电压,建议略高于实际截止电压

function readBatteryVoltage() {
  var adcVoltage = analogRead(PIN.batVoltage); // 读取的是分压后的电压
  var realVoltage = adcVoltage * VOLTAGE_DIVIDER_RATIO;

  // 简单线性计算电量百分比(仅供参考,锂电池放电曲线非绝对线性)
  var percent = (realVoltage - BATTERY_EMPTY) / (BATTERY_FULL - BATTERY_EMPTY) * 100;
  state.batteryPercent = Math.max(0, Math.min(100, Math.round(percent)));

  return realVoltage;
}

注意 :锂电池电量计算是个复杂问题,电压法只是一个粗略估计。更准确的方法是库仑计(测量进出电荷),但电路复杂。对于遥控器,电压估算法已完全够用。可以在主循环中每5秒调用一次此函数,避免频繁ADC读取。

3. 驱动OLED显示 ( updateDisplay )

function updateDisplay() {
  var g = global.G;
  if (!g) return;

  g.clear();
  g.setFontVector(20); // 使用大号字体显示主要信息

  // 根据显示页面切换内容
  switch(state.displayPage) {
    case 0: // 页面0:主要信息
      g.drawString("THR:" + state.throttle, 0, 0);
      g.setFontBitmap();
      g.drawString("BAT:" + state.batteryPercent + "%", 0, 25);
      g.drawString(state.isConnected ? "CONN" : "NO CONN", 70, 25);
      break;
    case 1: // 页面1:电压/调试信息
      var volt = readBatteryVoltage();
      g.setFontVector(16);
      g.drawString(volt.toFixed(2) + "V", 0, 0);
      g.setFontBitmap();
      g.drawString("ADC:" + analogRead(PIN.hallSensor).toFixed(3), 0, 20);
      break;
  }
  g.flip(); // 将缓冲区内容刷到屏幕
}

// 按键事件处理:切换显示页面
function onButtonPressed(e) {
  var duration = e.time - e.lastTime; // 粗略计算按下时长
  if (duration > 0.8) { // 长按
    // 进入配对模式或关机
    enterPairingMode();
  } else { // 短按
    state.displayPage = (state.displayPage + 1) % 2; // 在0和1之间切换
    updateDisplay();
  }
}

4. 低功耗蓝牙(BLE)通信 ( initBluetooth sendDataOverBLE ) 这是实现无线遥控的核心。我们将使用BLE的“自定义服务”(Custom Service)和“特征值”(Characteristic)来传输数据。

var bleServiceUUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"; // 自定义服务UUID
var txCharUUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"; // 发送特征 (遥控器->滑板)
var rxCharUUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"; // 接收特征 (滑板->遥控器,可选)

function initBluetooth() {
  NRF.setAdvertising([], {
    name: "ESK8-Remote-" + NRF.getAddress().substr(-5),
    showName: true,
    connectable: true,
    scannable: true,
    discoverable: true,
  });

  NRF.setServices({
    [bleServiceUUID]: {
      [txCharUUID]: {
        value: [], // 初始值空
        maxLen: 20,
        writable: false,
        readable: true,
        notify: true, // 启用通知,滑板端可以订阅此特征值的变化
        description: "Throttle Data",
      },
      // 可以添加更多特征值,如接收滑板回传的速度、温度等
    }
  }, { advertise: [bleServiceUUID] });

  // 监听连接事件
  NRF.on('connect', function(addr) {
    console.log("Connected to", addr);
    state.isConnected = true;
    digitalWrite(PIN.led, 1); // LED亮表示已连接
  });

  NRF.on('disconnect', function(addr) {
    console.log("Disconnected");
    state.isConnected = false;
    digitalWrite(PIN.led, 0); // LED灭表示未连接
  });
}

function sendDataOverBLE() {
  if (!state.isConnected) return;

  // 将油门、电池电量等数据打包成一个数组缓冲区
  // 协议设计示例:前2个字节为油门值(有符号16位整数),第3个字节为电量百分比
  var buffer = new ArrayBuffer(3);
  var view = new DataView(buffer);
  view.setInt16(0, state.throttle, true); // true 表示小端字节序
  view.setUint8(2, state.batteryPercent);

  // 更新特征值,已连接的中央设备(滑板)会收到通知
  NRF.updateServices({
    [bleServiceUUID]: {
      [txCharUUID]: {
        value: buffer,
        notify: true,
      }
    }
  });
}

BLE协议设计要点 :这里定义了一个简单的3字节协议。在实际项目中,你可能需要传输更多数据,比如遥控器ID、校验和、按钮状态等。务必设计一个清晰、可扩展的数据结构。 notify: true 使得滑板端无需主动轮询,一旦遥控器更新数据,滑板能立即收到,这是实现低延迟的关键。

4.3 代码上传与固化

在Web IDE中编写调试完所有代码后,需要将其保存到Espruino的“闪存”中,实现上电自启动。

  1. 连接设备 :在Web IDE中点击“Connect”。
  2. 发送代码 :将完整的代码粘贴到右侧代码编辑器,点击“Send to Espruino”。此时代码在RAM中运行。
  3. 测试 :操作摇杆/滑块,观察屏幕显示和变量输出是否正常。用手机BLE扫描工具(如 nRF Connect )搜索设备名,尝试连接并查看服务/特征值。
  4. 保存固化 :在左侧控制台输入命令 save() 并回车。这会将当前内存中的代码保存到Flash。下次断电重启后,设备会自动运行这些代码。
  5. 安全备份 :点击Web IDE的“Save”按钮,将代码保存到本地电脑。

重要警告 save() 命令会覆盖之前的保存内容。如果新代码有致命错误导致设备无法启动,你可能需要“擦除”Flash。方法是:断开USB,按住MDBT42Q上的按钮(如果有的話,通常是BTN1),再插入USB,待红灯快速闪烁后松开,此时设备进入“引导加载程序”模式,再在IDE中点击“Connect”并发送 E.setBootCode() 等命令清空。具体操作请查阅Espruino官方文档关于你所用板型的恢复方法。 务必在代码稳定后再执行 save()

5. 系统调试、优化与问题排查

即使按照步骤操作,第一次也难免遇到问题。这里汇总了常见坑点和解决方案。

5.1 上电无反应或异常

  • 检查电源 :用万用表测量电池保护板输出电压是否正常(~3.7V-4.2V)。检查拨动开关是否接触良好。
  • 检查接线 :重点检查VCC和GND是否接反、虚焊。尤其是OLED和Espruino的供电。
  • 观察指示灯 :MDBT42Q上电后通常有电源指示灯(常亮)和状态LED(可能闪烁)。如果毫无反应,可能是主板损坏或电源问题。

5.2 屏幕不显示或花屏

  • 检查I2C地址 :SSD1306的I2C地址通常是0x3C。在控制台输入 I2C1.scan() 查看是否能扫描到设备。
  • 检查接线 :确认SDA、SCL是否接对,接触是否良好。尝试加上拉电阻(4.7kΩ到3.3V)。
  • 供电不足 :屏幕启动瞬间电流可能较大,如果电源线太细或电池电量低,可能导致初始化失败。尝试外接稳定3.3V电源测试。

5.3 油门读数不稳定或跳动

  • 电源噪声 :电机、无线模块等都可能引入噪声。在霍尔传感器的VCC和GND之间并联一个 0.1uF的陶瓷电容 ,可以很好地滤除高频噪声。
  • ADC参考电压 :确保Espruino的ADC参考电压稳定。如果使用电池直接供电,电压会随着放电缓慢下降,影响ADC精度。可以在代码中定期读取一个已知的、稳定的内部参考电压来进行软件补偿,但对于本项目,影响微乎其微。
  • 软件滤波 :在 readThrottle 函数中加入软件滤波算法,如移动平均滤波或一阶低通滤波。
    var throttleHistory = new Array(5).fill(0);
    function readThrottleWithFilter() {
      var raw = // ... 原始的ADC读取和映射计算
      throttleHistory.shift(); // 移除最旧的值
      throttleHistory.push(raw);
      var filtered = throttleHistory.reduce((a,b)=>a+b) / throttleHistory.length;
      state.throttle = Math.round(filtered * 1000);
    }
    

5.4 BLE无法连接或数据不更新

  • 设备未广播 :在手机BLE扫描工具中查看是否能发现“ESK8-Remote-xxxxx”设备。如果没有,检查 initBluetooth 函数是否被执行,或者尝试在控制台手动执行 NRF.restart()
  • 服务/特征值未发现 :连接后,在 nRF Connect 中查看是否列出了我们定义的服务UUID和特征值UUID。确保UUID字符串格式正确。
  • 数据未通知 :确认在 sendDataOverBLE 中,更新特征值时设置了 notify: true 。同时,滑板端(中央设备)必须 订阅(Subscribe) 该特征值的通知,才能自动接收数据。
  • 连接不稳定 :检查天线周围是否有金属屏蔽。确保遥控器和滑板接收器之间的距离在合理范围内(开阔地通常10-20米没问题)。

5.5 续航时间短

  • 测量待机电流 :使用万用表电流档串联在电池和保护板之间,在遥控器待机(BLE保持广播)时测量电流。正常应在几百微安到几毫安之间。如果达到几十毫安,说明有地方漏电。
  • 优化软件
    • 降低屏幕刷新率,或者在不操作时关闭屏幕背光(如果支持)。
    • 降低主循环频率,比如从20Hz降到10Hz。
    • 在BLE连接后,可以适当降低广播功率( NRF.setAdvertising 中的 power 参数)。
    • 实现真正的休眠:当一段时间无操作后,让Espruino进入深度睡眠模式( deepSleep ),仅通过按键中断唤醒。这需要更复杂的电源电路设计和代码,是进阶优化方向。

完成以上所有步骤,你的自定义电动滑板遥控器就应该能够稳定工作了。从一堆散件到一个可以精准控制滑板加速减速的无线设备,这个过程中获得的硬件知识、嵌入式编程思维和解决问题的能力,远比一个成品遥控器更有价值。这个项目是一个完美的起点,你可以在此基础上继续扩展,比如增加震动反馈、GPS速度显示、甚至集成简单的游戏等。

更多推荐