在前两篇文章中,我们已经完成了控制与距离感应,建议先看完前两篇文章再来看这篇文章,不然你会看的一头雾水的。
在今天这篇文章中,我们需要解决的是将某些参数设置持久化储存在 ESP32 的储存器中,并且在重新上电运行时实时读取保存的参数。
而这些参数应该由手机通过蓝牙与 ESP32 通信来设置。
正如前面说过的,为了安全性,所以这里的与手机通讯会用回经典蓝牙。
Arduino 自带一个持久化储存的方案: EEPROM
EEPROM (Electrically Erasable Programmable Read-Only Memory),电可擦可编程只读存储器–一种掉电后数据不丢失的存储芯片。
EEPROM 使用也比较简单,首先导入头文件 #include 。
写入数据:
EEPROM.write(addr, val);
读取数据:
EEPROM.read(address);
可以看到,EEPROM 读取和写入都是直接按地址写/读字节,对于我们的需求:储存参数设置。十分的不方便,还需要我们自行处理保存地址和参数的关系。非常麻烦,因此我们放弃这个方案。
好在 ESP32 支持一种键值对的持久化储存方法:Preferences
Preferences 的键值对储存方法简直就是为了储存参数量身定制的。
先来简单的看一下怎么使用:
首先依然是导入头文件:
#include
然后定义一个 Preferences 对象:
Preferences prefs;
创建命名空间:
prefs.begin("setting");
写入:
prefs.putXXX("key", value);
读取:
prefs.getInt("key", defalutValue)
注意:上面是伪代码,运行不了的
因为 Preferences 储存的是键值对,所以不管是写入还是读取,都需要一个 key,且 key 只能是 string。
而写入的数据可以是所有 c++ 支持的数据类型,例如想要写入 int 则将XXX 替换为 int:
prefs.putInt("key", int_value);
需要注意的是,如果写入同一个命名空间的键(key)重复的话会覆盖(更新)已有键的值为新值。
读取数据时同理,如果需要读取 int 数据则为:
prefs.getInt("key", defalutIntValue)
第二个参数为默认值,如果没有查找到该键对应的值则返回这个默认值。
另外,prefs.end(); 用于关闭命名空间。
确定了方案后,我们来更改之前写的代码。
这里以 BLE 扫描持续为例,之前我们是直接硬编码了扫描持续时间为 5 s。
现在我们改为通过读取 Preferences 中的数据确定扫描持续时间:
- ....
- #define KEY_SCAN_TIME "SCAN_TIME"
- ....
- void start_scan() {
- isScanedDevice = 0;
- BLEDevice::init("");
- BLEScan* pBLEScan = BLEDevice::getScan();
- pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
- pBLEScan->setActiveScan(true);
- BLEScanResults foundDevices = pBLEScan->start(prefs.getInt(KEY_SCAN_TIME, 5)); // 扫描持续时间实时从 Preferences 读取
- if (isScanedDevice == 0) {
- Serial.println("Power Off by ble not found!");
-
- lock_car();
- }
- }
- ...
- 复制代码
而更新参数只需要在接受到新值时 prefs.putInt(KEY_SCAN_TIME, newValue); 即可。
上一节中我们已经说了如何持久化储存参数设置,但是光是能储存显然是不够的,我们还需要支持通过手机与 ESP32 通信更新参数。
在之前的代码中,我们已经实现了通过经典蓝牙让手机和 ESP32 交互数据。
但是有一个问题,ESP32 接收数据时是使用流的方式一个字节一个字节的接收的。
这意味着我们可能需要定义一个通信协议用来传输更新参数。
这里我们就简单定义如下:

第一个字节固定为 FF 表示起始标记;
第二个字节为需要设置的参数码,例如这里我把 01 定义为设置扫描持续时间;
第三个字节表示将该参数值设置为多少,例如这里表示更新扫描持续时间为 5;
第四个字节固定为 FF 表示结束标记。
其实这个协议非常粗糙,目前有几个问题:
代码如下:
- void loop()
- {
- if (!isConnectDevice) {
- start_scan(); // 开始搜索 BLE 设备
- }
-
- if (confirmRequestPending)
- {
- if (Serial.available())
- {
- int dat = Serial.read();
- if (dat == '1')
- {
- SerialBT.confirmReply(true);
- }
- else
- {
- SerialBT.confirmReply(false);
- }
- }
- }
- else
- {
- if (Serial.available())
- {
- SerialBT.write(Serial.read());
- }
- if (SerialBT.available())
- {
- int msg = SerialBT.read();
- Serial.write(msg);
- if (msg == 1) {
- get_start();
- Serial.println("rev 1");
- }
- else if (msg == 2) {
- cut_power();
- Serial.println("rev 2");
- }
- else if (msg == 3) {
- lock_car();
- Serial.println("rev 3");
- }
- else if (msg == 4) {
- luncher_car();
- Serial.println("rev 4");
- }
- else if (msg == 5) {
- shut_down_car();
- Serial.println("rev 5");
- }
- else if (msg == 6) {
- find_my_car();
- Serial.println("rev 6");
- }
- else if (msg == 7) {
- click_loop();
- Serial.println("rev 7");
- }
- else if (msg == 8) {
- click_lock();
- Serial.println("rev 8");
- }
- else if (msg == 9) {
- click_unlock();
- Serial.println("rev 9");
- } // 上面这几个(1-10)都是之前写的手动控制
- else if (msg == 255) { // 接受到设置参数的头字符
- Serial.println("rev 255");
- while (SerialBT.available()) {
- msg = SerialBT.read();
- if (setName == "null") { // 设置参数类型为空,在这里读取设置参数类型
- if (msg == 1) {
- Serial.println("rev 1 in setting");
- setName = KEY_SCAN_TIME; // 间隔时间
- }
- else if (msg == 2) {
- Serial.println("rev 2 in setting");
- setName = KEY_RSSI; // rssi 阈值
- }
- else if (msg == 3) {
- Serial.println("rev 3 in setting");
- setName = KEY_IS_UNLOCK; // 是否触发解锁
- }
- else { // 查找到未指定的参数类型,退出
- Serial.println("rev else in setting");
- Serial.printf("value=%d\n", msg);
- break;
- }
- }
- else if (msg == 255) { // 接收到请求设置参数结束符号
- Serial.println("rev 0 in setting");
- break;
- }
- else { // 这里是参数的具体数值,开始保存(参数值不能设置成 255 否则会和结束符号冲突)
- Serial.println("rev else in setting");
- Serial.printf("new value=%d\n", msg);
- prefs.putInt(setName, msg);
-
- setName = "null"; // 重置参数名
- }
- }
- }
- else if (msg == 10) {
- Serial.println("rev 10");
- read_status();
- }
- }
- }
- }
- 复制代码
运行效果如下:


上面图 2 是 手机端的截图。
图 3 是 ESP32 的串口日志。
手机端的截图它自动把十六进制数据转换成了 ACII 字符,所以,^J 表示 A;^A 表示 1;“乱码”表示的是 FF。
可以看到,我在手机端先发送了一个读取状态指令 A(10),其中扫描持续时间 scale_time 现在是 10 。
然后我发送了一个更新 01 (持续时间)参数为 01 的指令。
最后又发送了一个读取状态指令,可以看到持续时间已经成功被改为 1 了。
对了,读取状态的代码为:
- void read_status() {
- int power_value = digitalRead(PIN_POWER);
- int loop_value = digitalRead(PIN_LOOP);
- int lock_value = digitalRead(PIN_LOCK);
- int unlock_value = digitalRead(PIN_UNLOCK);
- int scale_time = prefs.getInt(KEY_SCAN_TIME, 5);
- int rssi = prefs.getInt(KEY_RSSI, 100);
- int is_unlock = prefs.getInt(KEY_IS_UNLOCK, 0);
-
- char s[200];
- sprintf(s, "Pin Status: \n power %d, loop %d, lock %d, unlock %d\n Setting Value: \n scale_time %d, rssi %d, is_unlock %d", power_value, loop_value, lock_value, unlock_value, scale_time, rssi, is_unlock);
- SerialBT.print(s);
- }
- 复制代码
自此,ESP32 程序基本已经为完全完成了,下一步是编写一个专属的控制 APP,毕竟不可能每次更新参数都要手撸代码吧,多麻烦啊,写个傻瓜式 APP 一键设置多方便啊。
哈哈,终于可以干回我的老本行:安卓 了。