基于 ESP32-C3 pro mini 的蓝牙电门锁控制设备。手机连接后自动导通 MOS 开关管,断开后自动关闭。使用自复位按钮控制蓝牙广播,配对码固定为 123456。
功能特性
- 蓝牙名称:
EBike - 配对码:
123456(静态,可在代码中修改) - HID 类型:消费者控制(Consumer Control)
- 按下 GPIO3 按钮开始广播,GPIO8 LED(板载LED)闪亮,再次按下停止广播
- 蓝牙连接成功 → GPIO2 输出高电平,MOS 导通,GPIO8 LED 点亮
- 蓝牙断开 → GPIO2 输出低电平,MOS 断开,GPIO8 LED 熄灭
- 未连接且未广播时,按按钮启动广播;已连接时按钮无效
- 每次都需要按按钮启动广播,降低待机功耗,加强安全性
硬件清单
| 名称 | 数量 | 说明 |
|---|---|---|
| ESP32-C3 Pro Mini | 1 | 主控板 |
| 双 MOS 管开关控制板 | 1 | 控制负载通断 |
| 自复位按钮 | 1 | 触发广播 |
| 公对母杜邦线 | 若干 | 连接用 |
| LED(可选) | 1 | 若板载 GPIO8 LED 不可用,可以选择性外接 |
| 负载(不在模块范围内) | 1 | 电动车电门锁 |
ESP32-C3 pro mini
双mos管开关控制板
接线说明
ESP32-C3 与按钮(如图)
| ESP32-C3 | 按钮 | 说明 |
|---|---|---|
| GPIO3 | 一端 | 另一端接 GND |
| GND | 另一端 | 共地 |

按钮使用内部上拉,无需外接上拉电阻。
ESP32-C3 与双 MOS 管控制板(如模拟图,不完全正确)
| ESP32-C3 | MOS 控制板 | 电门锁 | 说明 |
|---|---|---|---|
| GPIO2 | TRIG/PWM | - | 高电平导通,低电平断开 |
| GND | GND | - | 必须与主控板共地 |
| - | OUT+ | 控制器线 | - |
| - | VCC+ | 12V供电线 | - |

MOS 控制板的输出端接负载,注意负载电流不要超过 MOS 板额定值。
ESP32-C3 与 LED
| ESP32-C3 | LED | 说明 |
|---|---|---|
| GPIO8 | 正极(串限流电阻) | 高电平点亮 |
| GND | 负极 | 共地 |
若使用板载 LED,请确认 GPIO8 是否为普通 GPIO。部分开发板 GPIO8 连接的是 WS2812,需改用相应库驱动。
软件环境
- Arduino IDE 2.x
- ESP32 Arduino 核心(我的环境为3.3.12版本)
- NimBLE-Arduino 2.5.1
- 开发板选择:
ESP32C3 Dev Module - Flash 模式:
DIO - 上传速度:
921600(可按实际调整)
库安装
- 打开 Arduino IDE → 工具 → 管理库
- 搜索
NimBLE-Arduino - 选择版本
2.5.1安装 - 确保已安装 ESP32 开发板支持包
编译与上传
- 将代码复制到 Arduino IDE 新建的
.ino文件中 - 选择正确的开发板和端口
- 点击编译上传
使用方法
- 上电后,ESP32 不广播,MOS 断开,LED 熄灭
- 按下按钮(GPIO3),LED闪亮
- 手机打开蓝牙,搜索
EBike并点击连接 - 手机弹出配对码输入框,输入
123456 - 连接成功:MOS 导通,LED 常亮
- 手机主动断开或超出范围:MOS 断开,LED 熄灭
- 再次按下按钮可停止广播;已连接状态下按钮无效
- 断开后需重新按按钮启动广播
配对码说明
- 固定配对码:
123456 - 安卓手机连接 HID 设备时会强制弹出配对码输入框
- 如需修改,请更改代码中的:
#define STATIC_PASSKEY 123456
注意事项
- ESP32-C3 GPIO 为 3.3V 电平,请确认 MOS 控制板支持 3.3V 控制信号
- 双 MOS 管控制板用于大电流时,务必注意散热和线径
- ESP32 与 MOS 控制板 必须共地
- GPIO2 和 GPIO8 是 ESP32-C3 的 strapping 引脚,外接电路可能影响启动。若无法启动,请先断开外设再试
- 蓝牙连接后手机可能显示为“输入设备”,但实际无键盘功能,不会影响输入法
- 若无法配对,请删除手机蓝牙记录,重启 ESP32 后重新按按钮广播再配对
- NimBLE 2.5.1 与 ESP32 核心版本可能存在兼容性问题,建议使用推荐版本
常见问题
Q:手机搜索不到 EBike?
A:确认已按下按钮启动广播;查看串口是否输出“广播已启动”。
Q:连接后 MOS 不导通?
A:检查 GPIO2 接线、MOS 板供电、是否共地;用万用表测量 GPIO2 是否为高电平。
Q:配对码输入后连接失败?
A:删除手机蓝牙记录,重启 ESP32,重新按按钮广播再配对。
Q:上传失败?
A:检查开发板选择、端口、USB 线;ESP32-C3 可能需要按住 BOOT 键进入下载模式。
#include <Arduino.h>
#include <NimBLEDevice.h>
#include <NimBLEHIDDevice.h>
#include <HIDTypes.h>
// ==================== 引脚定义 ====================
#define BUTTON_PIN 3 // 广播触发按钮
#define MOS_PIN 2 // MOS 开关管控制引脚
#define LED_PIN 8 // ESP32 板载 LED
// ==================== 配对码 ====================
#define STATIC_PASSKEY 123456 // 6 位配对码,可自行修改
// ==================== LED 闪烁参数 ====================
#define LED_BLINK_INTERVAL 300 // 广播时闪烁间隔(毫秒)
// ==================== 全局对象 ====================
NimBLEHIDDevice* hid = nullptr;
NimBLECharacteristic* inputReport = nullptr;
bool isConnected = false; // 蓝牙连接状态
bool isAdvertising = false; // 广播状态
uint8_t lastButtonState = LOW; // 按钮上次状态
unsigned long lastBlinkTime = 0; // 上次 LED 翻转时间
bool ledBlinkState = false; // 当前闪烁电平
// ==================== 蓝牙设备控制报告描述符 ====================
static const uint8_t kReportMap[] = {
0x05, 0x0C,
0x09, 0x01,
0xA1, 0x01,
0x85, 0x01,
0x09, 0xE9,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x01,
0x81, 0x02,
0x09, 0xEA,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x01,
0x81, 0x02,
0x95, 0x06,
0x81, 0x03,
0xC0
};
// ==================== 服务器回调 ====================
class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override {
isConnected = true;
isAdvertising = false;
digitalWrite(MOS_PIN, HIGH);
digitalWrite(LED_PIN, LOW); // 连接后 LED 常亮
NimBLEDevice::getAdvertising()->stop();
}
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override {
isConnected = false;
digitalWrite(MOS_PIN, LOW);
digitalWrite(LED_PIN, HIGH); // 断开后 LED 灭
isAdvertising = false;
}
};
// ==================== 初始化蓝牙 ====================
void initBLE() {
NimBLEDevice::init("EBike");
NimBLEDevice::setSecurityAuth(true, true, true);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
NimBLEDevice::setSecurityPasskey(STATIC_PASSKEY);
NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
NimBLEServer* server = NimBLEDevice::createServer();
server->setCallbacks(new ServerCallbacks());
hid = new NimBLEHIDDevice(server);
hid->setManufacturer("DIY");
hid->setPnp(0x02, 0xE502, 0xA111, 0x0210);
hid->setHidInfo(0x00, 0x01);
hid->setReportMap((uint8_t*)kReportMap, sizeof(kReportMap));
hid->startServices();
inputReport = hid->getInputReport(1);
}
// ==================== 开始广播 ====================
void startAdvertising() {
if (isAdvertising || isConnected) return;
NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
adv->setAppearance(HID_KEYBOARD);
adv->addServiceUUID(hid->getHidService()->getUUID());
adv->setName("biu-EBike");
adv->start();
isAdvertising = true;
// 重置闪烁状态
ledBlinkState = false;
lastBlinkTime = millis();
}
// ==================== 停止广播 ====================
void stopAdvertising() {
if (!isAdvertising) return;
NimBLEDevice::getAdvertising()->stop();
isAdvertising = false;
// 停止广播后 LED 灭
digitalWrite(LED_PIN, HIGH);
ledBlinkState = false;
}
// ==================== 按钮处理 ====================
void handleButton() {
uint8_t currentState = digitalRead(BUTTON_PIN);
if (currentState == LOW && lastButtonState == HIGH) {
delay(50);
if (digitalRead(BUTTON_PIN) == LOW) {
if (!isConnected && !isAdvertising) {
startAdvertising();
} else if (isAdvertising) {
stopAdvertising();
}
}
}
lastButtonState = currentState;
}
// ==================== LED 状态更新 ====================
void updateLED() {
if (isConnected) {
// 已连接:常亮
digitalWrite(LED_PIN, LOW);
} else if (isAdvertising) {
// 广播中:闪烁
unsigned long now = millis();
if (now - lastBlinkTime >= LED_BLINK_INTERVAL) {
lastBlinkTime = now;
ledBlinkState = !ledBlinkState;
digitalWrite(LED_PIN, ledBlinkState ? LOW : HIGH);
}
} else {
// 空闲:灭
digitalWrite(LED_PIN, HIGH);
ledBlinkState = false;
}
}
// ==================== 主循环中的蓝牙状态检查 ====================
void checkBLEStatus() {
if (isAdvertising && isConnected) {
stopAdvertising();
}
}
// ==================== setup ====================
void setup() {
Serial.begin(115200);
delay(500);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(MOS_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(MOS_PIN, LOW);
digitalWrite(LED_PIN, HIGH);
initBLE();
}
// ==================== loop ====================
void loop() {
handleButton();
checkBLEStatus();
updateLED();
if (isConnected) {
digitalWrite(MOS_PIN, HIGH);
}
delay(10);
}
评论组件加载中…