不止于点灯:用XIAO ESP32-C3的EEPROM和蓝牙WiFi,做个能“记住”的物联网小项目
从零构建智能记忆灯:XIAO ESP32-C3的EEPROM与双模通信实战
去年夏天,我在工作室调试智能灯带时遇到一个尴尬场景:每次断电重启后,所有个性化设置都会重置。这种"失忆"问题在物联网设备中相当常见,而解决它的关键就在于如何让设备"记住"用户偏好。今天我们就用XIAO ESP32-C3这款仅拇指大小的开发板,打造一个能通过蓝牙接收指令、自动保存配置到EEPROM,并支持WiFi双模连接的智能灯控系统。
1. 项目架构设计
这个项目的核心在于建立三层数据流架构:
- 用户交互层:通过蓝牙接收手机发送的指令(开关状态、亮度值、WiFi凭证等)
- 持久化层:使用EEPROM非易失存储保存关键配置
- 网络层:根据存储的凭证自动连接WiFi或启动AP热点
[图表已移除:根据规范要求不使用mermaid图表]实际开发中,我们需要解决几个关键问题:
- EEPROM的写入寿命限制(约10万次)
- 蓝牙与WiFi射频共存时的干扰处理
- 配置数据的校验与容错机制
2. EEPROM数据持久化实战
ESP32-C3采用NVS(Non-Volatile Storage)实现EEPROM功能,其存储结构类似键值数据库。我们先构建一个安全的数据存储模块:
#include <Preferences.h> #define MAX_SSID_LEN 32 #define MAX_PASS_LEN 64 struct DeviceConfig { char ssid[MAX_SSID_LEN]; char password[MAX_PASS_LEN]; uint8_t brightness; bool powerState; }; Preferences preferences; void saveConfig(const DeviceConfig &config) { preferences.begin("iot-light", false); preferences.putBytes("config", &config, sizeof(config)); // 添加CRC校验 uint32_t crc = calculateCRC32((uint8_t*)&config, sizeof(config)); preferences.putUInt("crc", crc); preferences.end(); } bool loadConfig(DeviceConfig &config) { preferences.begin("iot-light", true); size_t len = preferences.getBytesLength("config"); if(len == sizeof(config)) { preferences.getBytes("config", &config, len); uint32_t savedCRC = preferences.getUInt("crc", 0); uint32_t calcCRC = calculateCRC32((uint8_t*)&config, len); if(savedCRC == calcCRC) { preferences.end(); return true; } } preferences.end(); return false; }关键优化点:
- 使用结构体打包所有配置参数
- 添加CRC32校验防止数据损坏
- 限制写入频率(如配置变更后延迟5秒保存)
注意:NVS存储空间有限(通常1MB左右),建议单个命名空间不超过512KB
3. 蓝牙指令系统实现
我们采用BLE GATT服务构建双向通信通道,定义以下特征值:
| UUID | 特征名 | 权限 | 数据类型 | 说明 |
|---|---|---|---|---|
| 0xA001 | Power | RW | bool | 开关状态 |
| 0xA002 | Brightness | RW | uint8 | 亮度(0-100) |
| 0xA003 | WiFiConfig | Write | json | {"ssid":"","pass":""} |
| 0xA004 | Status | Notify | string | 设备状态推送 |
完整服务实现代码:
#include <BLEDevice.h> #include <BLE2902.h> BLECharacteristic *pPowerChar; BLECharacteristic *pBrightnessChar; DeviceConfig currentConfig; class BluetoothCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pChar) { std::string value = pChar->getValue(); uint16_t handle = pChar->getHandle(); switch(handle) { case POWER_HANDLE: currentConfig.powerState = *(bool*)value.data(); updateLED(); break; case BRIGHTNESS_HANDLE: currentConfig.brightness = *(uint8_t*)value.data(); updateLED(); break; case WIFI_HANDLE: { DynamicJsonDocument doc(256); deserializeJson(doc, value); strncpy(currentConfig.ssid, doc["ssid"], MAX_SSID_LEN); strncpy(currentConfig.password, doc["pass"], MAX_PASS_LEN); saveConfig(currentConfig); wifiReconnect(); break; } } } }; void setupBLE() { BLEDevice::init("SmartLight-01"); BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); pPowerChar = pService->createCharacteristic( POWER_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pPowerChar->setCallbacks(new BluetoothCallbacks()); // 其他特征值初始化... pService->start(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->start(); }蓝牙连接优化技巧:
- 设置合适的MTU大小(建议517字节)
- 启用BLE安全配对(Just Works模式)
- 实现连接参数更新请求(如间隔20ms,延迟500ms)
4. 双模WiFi网络管理
设备需要智能判断网络环境,我的实现方案是:
- 优先尝试连接已保存的WiFi网络(最多尝试3次)
- 失败后启动配置AP(带Web配置页面)
- 连接成功后切换回STA模式
#include <WiFi.h> #include <WebServer.h> WebServer configServer(80); void startAPMode() { WiFi.softAP("SmartLight-Config", "setup123"); configServer.on("/", HTTP_GET, [](){ String html = "<form action='/save' method='POST'>" "SSID: <input type='text' name='ssid'><br>" "Password: <input type='password' name='pass'><br>" "<input type='submit' value='Save'>" "</form>"; configServer.send(200, "text/html", html); }); configServer.on("/save", HTTP_POST, [](){ strncpy(currentConfig.ssid, configServer.arg("ssid").c_str(), MAX_SSID_LEN); strncpy(currentConfig.password, configServer.arg("pass").c_str(), MAX_PASS_LEN); saveConfig(currentConfig); configServer.send(200, "text/plain", "Config Saved"); delay(1000); ESP.restart(); }); configServer.begin(); } bool connectToWiFi() { WiFi.begin(currentConfig.ssid, currentConfig.password); for(int i=0; i<30; i++) { if(WiFi.status() == WL_CONNECTED) { return true; } delay(1000); } return false; } void wifiManager() { if(strlen(currentConfig.ssid) > 0) { if(connectToWiFi()) { Serial.println("WiFi Connected"); return; } } startAPMode(); while(true) { configServer.handleClient(); delay(1); } }网络性能优化:
- 设置合理的WiFi.sleepType(WIFI_PS_NONE禁用节电模式)
- 实现OTA升级服务(需保留至少1MB Flash空间)
- 添加mDNS服务(如
smartlight.local访问)
5. 系统集成与电源管理
将各模块整合时,需要特别注意资源竞争问题。这是我的任务调度方案:
void loop() { static uint32_t lastSave = 0; // 蓝牙事件处理 if(millis() - lastBLEEvent < 5000) { // 高优先级处理 vTaskDelay(1); } // 网络事件处理 if(WiFi.status() == WL_CONNECTED) { server.handleClient(); } // 延迟保存配置 if(needSave && millis() - lastSave > 5000) { saveConfig(currentConfig); needSave = false; lastSave = millis(); } // 低功耗处理 if(!digitalRead(USER_BTN)) { enterDeepSleep(); } }电源优化技巧:
- 使用esp_sleep_enable_timer_wakeup()实现定时唤醒
- 关闭未使用的硬件外设(如ADC、温度传感器)
- 调整CPU频率(如设置为80MHz)
6. 项目进阶方向
完成基础功能后,可以考虑以下扩展:
场景模式:
# 通过蓝牙发送JSON指令 { "scene": "sunset", "duration": 1800, "colors": [ {"r":255,"g":100,"b":0}, {"r":255,"g":50,"b":0} ] }能耗监控:
float getPowerConsumption() { float voltage = analogRead(VBAT_PIN) * 3.3 / 4095 * 2; float current = analogRead(CURRENT_PIN) * 0.1; // 根据传感器调整 return voltage * current; }物理交互:
- 电容触摸控制
- 加速度计手势识别
- 环境光自适应调节
在最近的一个客户项目中,我们采用类似架构实现了美术馆智能照明系统。通过BLE信标定位参观者位置,灯光能自动跟随移动并保存不同展区的亮度偏好。这个案例证明,即使简单的记忆功能也能显著提升用户体验。
