Hướng dẫn lưu dữ liệu vào bộ nhớ flash của ESP32 bằng thư viện Preferences
- 1. Dữ liệu nào còn, dữ liệu nào mất?
- 2. Thư viện Preferences là gì?
- 3. Linh kiện cần chuẩn bị
- 4. Chương trình 1: đếm số lần khởi động
- 5. Giải thích các lệnh chính
- 6. Chương trình 2: nhớ trạng thái LED hoặc relay sau khi mất điện
- 7. Chương trình 3: lưu thông số cài đặt
- 8. Ghi flash đúng cách để bộ nhớ bền
- 9. Xóa dữ liệu đã lưu
- 10. Xử lý lỗi thường gặp
Hướng dẫn dùng thư viện Preferences trên ESP32 với Arduino IDE để lưu số đếm, trạng thái relay và thông số cài đặt vào bộ nhớ flash, không mất khi tắt nguồn.
Relay đang bật thì mất điện, có điện lại nó nên bật tiếp hay tắt? Cân đã hiệu chuẩn xong, tắt nguồn rồi có phải hiệu chuẩn lại không? Muốn thiết bị nhớ được những thông tin như vậy, dữ liệu phải được ghi vào bộ nhớ flash. Bài viết hướng dẫn dùng thư viện Preferences có sẵn của ESP32 trên Arduino IDE để lưu số đếm, trạng thái và thông số cài đặt sao cho không mất khi tắt nguồn.
1. Dữ liệu nào còn, dữ liệu nào mất?
- Biến thông thường nằm trong RAM. Mỗi lần ESP32 khởi động lại, biến trở về giá trị ban đầu.
- Biến khai báo với
RTC_DATA_ATTRgiữ được giá trị qua các lần Deep Sleep, nhưng vẫn mất khi mất nguồn hoặc khi nhấn nút EN. - Dữ liệu ghi vào bộ nhớ flash còn nguyên qua mọi lần khởi động lại và mất nguồn. Nạp chương trình mới cũng không làm mất dữ liệu này.
2. Thư viện Preferences là gì?
Preferences là thư viện đi kèm gói esp32, dùng để lưu các giá trị nhỏ vào một phân vùng riêng của bộ nhớ flash gọi là NVS. Dữ liệu được tổ chức theo ba khái niệm:
- Vùng lưu trữ (namespace): một nhóm dữ liệu có tên riêng, giúp các phần của chương trình không ghi đè lên nhau.
- Khóa (key): tên của một giá trị trong vùng. Tên vùng và tên khóa dài tối đa 15 ký tự.
- Giá trị: có kiểu rõ ràng. Ghi bằng lệnh
putkiểu nào thì phải đọc bằng lệnhgetđúng kiểu đó.
| Kiểu dữ liệu | Lệnh ghi | Lệnh đọc |
|---|---|---|
| Đúng / sai | putBool(khóa, giá trị) | getBool(khóa, mặc định) |
| Số nguyên có dấu 32 bit | putInt() | getInt() |
| Số nguyên không dấu 32 bit | putUInt() | getUInt() |
| Số thực | putFloat() | getFloat() |
| Chuỗi ký tự | putString() | getString() |
| Mảng byte, struct | putBytes() | getBytes() |
Preferences thay cho thư viện EEPROM quen thuộc trên Arduino Uno. Với dữ liệu lớn như file nhật ký, nên dùng thẻ nhớ theo bài dùng module thẻ nhớ Micro SD với ESP32.
3. Linh kiện cần chuẩn bị
- ESP32 DevKit V1 30 chân (hoặc bản dùng chip CH340) và cáp USB có truyền dữ liệu.
- Máy tính đã cài Arduino IDE 2.x và gói esp32 by Espressif Systems (bài dùng phiên bản 3.3.10). Không cần cài thêm thư viện.
Cả ba chương trình trong bài chỉ dùng LED và nút BOOT có sẵn trên mạch, không cần nối thêm linh kiện.
4. Chương trình 1: đếm số lần khởi động
Mỗi lần khởi động, chương trình đọc số đếm đã lưu, cộng thêm 1 rồi ghi lại:
/*
PYWORLD.VN - ESP32 Preferences: đếm số lần khởi động, số đếm không mất khi tắt nguồn
Chỉ cần mạch ESP32
Thư viện Preferences có sẵn trong gói esp32, không cần cài thêm
*/
#include <Preferences.h>
Preferences boNho;
void setup() {
Serial.begin(115200);
delay(500);
// Mở vùng lưu trữ tên "thongke"; false = được phép đọc và ghi
boNho.begin("thongke", false);
// Đọc khóa "dem"; nếu chưa từng lưu thì nhận giá trị mặc định 0
unsigned int soLan = boNho.getUInt("dem", 0);
soLan++;
boNho.putUInt("dem", soLan); // Ghi giá trị mới vào bộ nhớ flash
boNho.end(); // Đóng vùng lưu trữ
Serial.printf("\nESP32 da khoi dong %u lan.\n", soLan);
Serial.println("Nhan nut EN hoac rut nguon cam lai de thay so dem tang.");
}
void loop() {
}
Chọn board DOIT ESP32 DEVKIT V1, nạp chương trình và mở Serial Monitor ở 115200 baud. Nhấn nút EN vài lần, sau đó rút cáp USB rồi cắm lại: số đếm vẫn tăng tiếp chứ không quay về 1.
5. Giải thích các lệnh chính
boNho.begin("thongke", false): mở vùng lưu trữ tên thongke. Tham sốfalsecho phép cả đọc và ghi;truelà chỉ đọc. Nếu vùng chưa tồn tại, chế độ đọc và ghi sẽ tự tạo.boNho.getUInt("dem", 0): đọc giá trị của khóa dem. Số 0 là giá trị trả về khi khóa chưa từng được lưu, ví dụ ở lần chạy đầu tiên.boNho.putUInt("dem", soLan): ghi giá trị mới vào flash.boNho.end(): đóng vùng lưu trữ khi không dùng nữa.boNho.isKey("heso")(dùng ở chương trình 3): cho biết khóa đã tồn tại hay chưa.boNho.remove("heso")xóa một khóa;boNho.clear()xóa mọi khóa trong vùng đang mở.
6. Chương trình 2: nhớ trạng thái LED hoặc relay sau khi mất điện
Nhấn nút BOOT để bật hoặc tắt LED trên mạch. Trạng thái được lưu lại ngay, và khi có điện trở lại ESP32 khôi phục đúng trạng thái đó. Cách làm với relay hoàn toàn giống vậy, chỉ cần đổi chân LED thành chân điều khiển relay.
/*
PYWORLD.VN - ESP32 Preferences: nhớ trạng thái LED (hoặc relay) sau khi mất điện
Nút BOOT có sẵn trên mạch (GPIO0): nhấn để bật, tắt LED
LED có sẵn trên mạch DevKit V1: GPIO2
Thư viện Preferences có sẵn trong gói esp32, không cần cài thêm
*/
#include <Preferences.h>
#define NUT_PIN 0
#define LED_PIN 2
Preferences boNho;
bool ledDangBat = false;
bool nutLanTruoc = HIGH;
void setup() {
Serial.begin(115200);
delay(500);
pinMode(NUT_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
// Khôi phục trạng thái đã lưu; lần chạy đầu tiên dùng giá trị mặc định false (tắt)
boNho.begin("thietbi", false);
ledDangBat = boNho.getBool("led", false);
digitalWrite(LED_PIN, ledDangBat);
Serial.printf("\nKhoi phuc trang thai: LED %s\n", ledDangBat ? "BAT" : "TAT");
}
void loop() {
bool nut = digitalRead(NUT_PIN);
// Phát hiện lúc vừa nhấn nút (mức cao chuyển xuống mức thấp)
if (nutLanTruoc == HIGH && nut == LOW) {
ledDangBat = !ledDangBat;
digitalWrite(LED_PIN, ledDangBat);
// Chỉ ghi vào flash khi trạng thái thay đổi, không ghi liên tục trong loop()
boNho.putBool("led", ledDangBat);
Serial.printf("Da luu: LED %s\n", ledDangBat ? "BAT" : "TAT");
delay(50); // Chống dội đơn giản
}
nutLanTruoc = nut;
}
Ví dụ kết quả khi nhấn nút một lần, rút nguồn rồi cắm lại (số minh họa):
Khoi phuc trang thai: LED TAT
Da luu: LED BAT
Khoi phuc trang thai: LED BAT
Chỉ nhấn nút BOOT khi chương trình đang chạy. Nếu giữ BOOT trong lúc cấp nguồn hoặc lúc nhấn nút EN, ESP32 sẽ vào chế độ chờ nạp chương trình.
7. Chương trình 3: lưu thông số cài đặt
Nhiều thiết bị cần nhớ một vài thông số do người dùng nhập: hệ số hiệu chuẩn của cân, ngưỡng cảnh báo, thời gian trễ. Chương trình sau nhận một số từ Serial Monitor và lưu lại; lần khởi động sau nó tự đọc lên.
/*
PYWORLD.VN - ESP32 Preferences: lưu một thông số cài đặt nhập từ Serial Monitor
Gõ một số (ví dụ 420.5) rồi nhấn Enter để lưu; gõ xoa rồi nhấn Enter để xóa
Thư viện Preferences có sẵn trong gói esp32, không cần cài thêm
*/
#include <Preferences.h>
const float HE_SO_MAC_DINH = 1.0;
Preferences boNho;
float heSo = HE_SO_MAC_DINH;
void setup() {
Serial.begin(115200);
delay(500);
boNho.begin("caidat", false);
// isKey() cho biết khóa đã từng được lưu hay chưa
if (boNho.isKey("heso")) {
heSo = boNho.getFloat("heso", HE_SO_MAC_DINH);
Serial.printf("\nDa doc he so da luu: %.3f\n", heSo);
} else {
Serial.printf("\nChua co he so da luu, dung mac dinh: %.3f\n", heSo);
}
Serial.println("Go mot so roi nhan Enter de luu. Go xoa de xoa.");
}
void loop() {
if (!Serial.available()) {
return;
}
String dong = Serial.readStringUntil('\n');
dong.trim();
dong.replace(',', '.');
if (dong.length() == 0) {
return;
}
if (dong == "xoa") {
boNho.remove("heso");
heSo = HE_SO_MAC_DINH;
Serial.println("Da xoa. He so quay ve mac dinh.");
return;
}
float giaTri = dong.toFloat();
if (giaTri == 0) {
Serial.println("Gia tri khong hop le.");
return;
}
// Chỉ ghi vào flash khi giá trị thay đổi
if (giaTri != heSo) {
heSo = giaTri;
boNho.putFloat("heso", heSo);
}
Serial.printf("He so hien tai: %.3f (da luu)\n", heSo);
}
Trong Serial Monitor, chọn kiểu kết thúc dòng New Line, gõ 420.5 rồi nhấn Enter. Nhấn nút EN để khởi động lại và kiểm tra (số minh họa):
Chua co he so da luu, dung mac dinh: 1.000
Go mot so roi nhan Enter de luu. Go xoa de xoa.
He so hien tai: 420.500 (da luu)
Da doc he so da luu: 420.500
Go mot so roi nhan Enter de luu. Go xoa de xoa.
8. Ghi flash đúng cách để bộ nhớ bền
Mỗi ô nhớ flash chỉ chịu được một số lần xóa ghi giới hạn, cỡ một trăm nghìn lần. NVS tự xoay vòng vị trí ghi nên tuổi thọ thực tế cao hơn nhiều, nhưng vẫn cần ghi có chừng mực:
- Chỉ ghi khi giá trị thay đổi. Cả ba chương trình trong bài đều ghi theo sự kiện, không ghi lặp lại trong
loop(). - Không lưu số đo thay đổi liên tục như nhiệt độ từng giây. Nếu cần, hãy gom lại và ghi thưa, ví dụ mỗi giờ một lần.
- Số đếm thay đổi nhanh trong thiết bị dùng Deep Sleep nên để ở bộ nhớ RTC, thỉnh thoảng mới chép sang flash.
- Dung lượng nhỏ: phân vùng NVS mặc định chỉ khoảng 20 KB, phù hợp với cài đặt và trạng thái, không dùng để lưu dữ liệu lớn.
9. Xóa dữ liệu đã lưu
- Xóa một khóa:
boNho.remove("tên khóa"). - Xóa cả vùng đang mở:
boNho.clear(). - Xóa toàn bộ flash, kể cả dữ liệu Preferences: trong Arduino IDE chọn Tools > Erase All Flash Before Sketch Upload > Enabled rồi nạp lại chương trình. Sau đó nên đặt lại thành Disabled.
10. Xử lý lỗi thường gặp
- Giá trị không được lưu: tên vùng hoặc tên khóa dài quá 15 ký tự, hoặc vùng đang mở ở chế độ chỉ đọc (
begin(tên, true)). - Đọc ra luôn là giá trị mặc định: đọc sai kiểu so với lúc ghi (ví dụ ghi bằng
putInt()nhưng đọc bằnggetFloat()), hoặc gõ sai tên khóa. Tên khóa có phân biệt chữ hoa, chữ thường. - Mở vùng ở chế độ chỉ đọc mà không đọc được gì: vùng đó chưa từng được tạo, khi ấy
begin()trả vềfalsevà mọi lệnh đọc đều ra giá trị mặc định. Mở vùng một lần ở chế độ đọc và ghi để tạo nó. - Dữ liệu biến mất sau khi nạp chương trình: tùy chọn Erase All Flash Before Sketch Upload đang bật.
- Muốn lưu mật khẩu Wi-Fi: làm được bằng
putString(), nhưng dữ liệu NVS mặc định không được mã hóa. Không dùng cách này cho thông tin thật sự nhạy cảm. - Không nạp được chương trình: dùng cáp USB có truyền dữ liệu, chọn đúng cổng COM và giữ nút BOOT khi IDE dừng ở dòng Connecting...
Xem thêm: ESP32 làm web server điều khiển relay qua Wi-Fi, ESP32 điều khiển module relay 4 kênh và HX711 với load cell và LCD 1602 bằng MicroPython.
Xem thêm