Bộ nhớ Flash ESP32 – Cách lưu dữ liệu vĩnh viễn

Trong bài viết này, mình sẽ hướng dẫn bạn cách lưu trữ dữ liệu trên bộ nhớ Flash ESP32, thông qua phần mềm Arduino IDE. Các dữ liệu lưu trữ ở đây sẽ không bị mất, ngay cả khi bạn reset mạch ESP32 hay tắt đi mở lại. Để bạn hiểu rõ hơn, IoTZone sẽ hướng dẫn bạn cách lưu trạng thái cuối cùng của chân GPIO. Cùng tìm hiểu nhé!

Trước khi vào nội dung chính của bài, bạn nên cài tiện ích ESP32 trong phần mềmArduino IDE của mình theo hướng dẫn sau: Cách lập trình ESP32 bằng Arduino IDE (Windows, Linux, Mac OS X)

Bạn cũng có thể xem qua một số hướng dẫn cơ bản sau:

Chuẩn bị

Để làm theo hướng dẫn này, bạn cần chuẩn bị các  linh kiện điện tử sau:

  • Mạch ESP32
  • Đèn LED 5mm
  • Điện trở 330 Ohm
  • Nút nhấn
  • Điện trở 10k Ohm
  • Breadboad
  • Dây Jumper

Giới thiệu về bộ nhớ Flash ESP32

Dữ liệu được lưu trong bộ nhớ Flash ESP32 sẽ không mất đi, ngay cả khi bạn reset mạch hoặc ngắt nguồn điện. Bộ nhớ này khá giống với EEPROM, cả hai đều không dễ dàng bị mất.

Chúng ta thường lưu những dữ liệu cần thiết vào bộ nhớ Flash, chẳng hạn như:

  • Trạng thái cuối cùng của một biến
  • Lưu các cài đặt
  • Lưu số lần kích hoạt thiết bị, chương trình
  • Lưu tất cả các dữ liệu nào khác mà bạn muốn lưu trữ vĩnh viễn

Tuy nhiên, bộ nhớ Flash có giới hạn về số lần lưu trữ dữ liệu. Chúng ta có thể đọc thông tin dữ liệu từ Flash bao nhiêu lần tùy thích đều được, nhưng với lượt ghi dữ liệu, đa số các mạch ESP32 chỉ hỗ trợ khoảng 100.000 đến 1.000.000 lượt.

Sử dụng thư viện EEPROM để lưu dữ liệu vào Flash ESP32

Để đọc và ghi dữ liệu vào trong bộ nhớ Flash ESP32, chúng ta sẽ sử dụng một thư viện có tên là EEPROM. 

Cách sử dụng thư viện EEPROM với ESP32 cũng khá tương tự như khi sử dụng với Arduino. Do đó, nếu bạn đã từng sử dụng Arduino EEPROM trước đây thì bạn sẽ khá dễ dàng tiếp cận.

Hiện nay, bạn có thể sử dụng tối đa 512 bite để lưu trữ trong bộ nhớ Flash. Điều này đồng nghĩa với việc bạn chỉ có 512 địa chỉ khác nhau và mỗi địa chỉ chỉ có thể lưu giá trị từ 0 đến 255. 

Lưu dữ liệu

Để lưu dữ liệu vào bộ nhớ Flash ESP32, Chúng ta sẽ sử dụng câu lệnh sau, với thông tin gồm địa chỉ nơi bạn muốn lưu dữ liệu và giá trị (biến byte) mà bạn muốn lưu:

EEPROM.write(address, value);

Ví dụ cụ thể, câu lệnh dưới đây sẽ lưu giá trị 9 vào địa chỉ 0:

EEPROM.write(0, 9);

Chúng ta sẽ sử dụng hàm theo dõi sau để lưu những thay đổi:

EEPROM.commit();

Đọc dữ liệu

Để đọc giá trị đã được lưu trong bộ nhớ Flash, chúng ta sẽ sử dụng câu lệnh sau (với thông tin là địa chỉ nơi bạn muốn đọc dữ liệu):

EEPROM.read(address);

Mình sẽ ví dụ cụ thể như sau để bạn dễ hiểu. Dưới đây là câu lệnh để đọc giá trị byte đã được lưu trước đó ở địa chỉ 0:

EEPROM.read(0);

Câu lệnh trên sẽ trả về giá trị là 9 mà chúng ta đã lưu trước đó.

Lưu trạng thái cuối cùng của chân GPIO

Để hướng dẫn bạn cách lưu dữ liệu vào trong bộ nhớ Flash ESP32 trực quan hơn, chúng ta sẽ cùng thực hành lưu trạng thái cuối cùng của chân GPIO, cụ thể là đèn LED. 

Hãy cùng xem qua một tình huống thực tế:

  1. Bạn đang điều khiển một đèn LED bằng mạch ESP32
  2. Bạn bật đèn
  3. Đột ngột xảy ra tình trạng cúp điện
  4. Sau khi có điện lại, đèn vẫn tắt, vì nó không lưu trạng thái cuối cùng (trạng thái bật đèn)
Khi chưa lưu trạng thái LED cuối cùng vào bộ nhớ Flash ESP32
Khi chưa lưu trạng thái LED cuối cùng vào bộ nhớ Flash ESP32

Đây là hiện tượng không mong muốn. Bạn muốn ESP32 ghi nhớ những gì đã diễn ra trước đó và khi mạch ESP32 khởi động lại, đèn LED sẽ quay trở lại trạng thái cuối cùng (là trạng thái bật).

Để giải quyết vấn đề này, bạn có thể lưu trạng thái của đèn vào bộ nhớ Flash. Sau đó, bạn chỉ cần thêm một điều kiện để kiểm tra trạng thái đèn cuối cùng, rồi tiến hành bật hoặc tắt đèn tương ứng. 

Hình ảnh dưới này sẽ mô tả hoạt động của hệ thống sau khi chúng ta hoàn thành các bước trên:

Khi đã lưu trạng thái LED cuối cùng vào bộ nhớ Flash ESP32
Khi đã lưu trạng thái LED cuối cùng vào bộ nhớ Flash ESP32

Bây giờ, hãy cùng xem hướng dẫn chi tiết nhé!

Kết nối phần cứng

Trước tiên, bạn tiến hành kết nối nút nhấn và đèn LED vào trong mạch ESP32 theo như hình dưới:

Kết nối phần cứng để lưu dữ liệu vào bộ nhớ Flash ESP32

Lập trình

Mình đã chuẩn bị sẵn đoạn code cho bạn. Tất cả những gì bạn cần làm là copy và nạp nó vào trong mạch ESP32 của mình:

/*********
  Rui Santos
  Complete project details at https://randomnerdtutorials.com  
*********/

// include library to read and write from flash memory
#include <EEPROM.h>

// define the number of bytes you want to access
#define EEPROM_SIZE 1

// constants won't change. They're used here to set pin numbers:
const int buttonPin = 4;    // the number of the pushbutton pin
const int ledPin = 16;      // the number of the LED pin

// Variables will change:
int ledState = HIGH;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

void setup() { 
  Serial.begin(115200);
  
  // initialize EEPROM with predefined size
  EEPROM.begin(EEPROM_SIZE);

  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);

  // read the last LED state from flash memory
  ledState = EEPROM.read(0);
  // set the LED to the last stored state
  digitalWrite(ledPin, ledState);
}

void loop() {
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
        ledState = !ledState;
      }
    }
  }
  // save the reading. Next time through the loop, it'll be the lastButtonState:
  lastButtonState = reading;
  
  // if the ledState variable is different from the current LED state
  if (digitalRead(ledPin)!= ledState) {  
    Serial.println("State changed");
    // change the LED state
    digitalWrite(ledPin, ledState);
    // save the LED state in flash memory
    EEPROM.write(0, ledState);
    EEPROM.commit();
    Serial.println("State saved in flash memory");
  }
}

Dưới đây, mình sẽ giải thích chi tiết cách chương trình trên hoạt động.

Giải thích chương trình

Đầu tiên, mình sẽ giải thích tổng quan cách chương trình hoạt động. Chương trình này sẽ gỡ lỗi thay đổi trạng thái đèn LED mỗi khi bạn nhấn nút nhấn. Điều đặc biệt là chúng sẽ ghi nhớ trạng thái đèn LED cuối cùng, ngay cả khi bạn reset mạch hoặc là ngắt mạch khỏi nguồn điện. Cùng xem chi tiết:

Đầu tiên chúng ta sẽ sử dụng thư viện EEPROM:

Code 1 

#include <EEPROM.h>

Sau đó, bạn hãy xác định kích thước của EEPROM.  Đây là số byte mà bạn muốn truy cập trong bộ nhớ Flash ESP32. Cụ thể trong trường hợp này, chúng ta chỉ lưu trạng thái của đèn LED nên kích thước sẽ là 1:

#define EEPROM_SIZE 1

Chúng ta cũng cần phải khai báo những biến khác để chương trình chạy được:

// constants won't change. They're used here to set pin numbers: 
const int buttonPin = 4; // the number of the pushbutton pin 
const int ledPin = 16; // the number of the LED pin 

// Variables will change: 
int ledState = HIGH; 
// the current state of the output pin 
int buttonState; // the current reading from the input pin 
int lastButtonState = LOW; // the previous reading from the input pin 

// the following variables are unsigned longs because the time, measured in 
// milliseconds, will quickly become a bigger number than can be stored in an int. unsigned long lastDebounceTime = 0; // the last time the output pin was toggled unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers

Chương trình chính của chúng ta sẽ gồm hai phần chính: setup() và loop():

setup()

Khởi tạo EEPROM với kích thước đã được xác định trước:

EEPROM.begin(EEPROM_SIZE);

Để đảm bảo chương trình luôn lấy trạng thái đèn LED mới nhất, bạn nên đọc trạng thái đèn LED cuối cùng từ bộ nhớ Flash. Chúng được lưu trữ ở địa chỉ 0:

ledState = EEPROM.read(0);

Bạn chỉ cần bật hoặc tắt đèn LED tương ứng với giá trị đọc được từ bộ nhớ Flash:

digitalWrite (ledPin, ledState);

loop()

Chương trình dưới đây sẽ kiểm tra xem nút nhấn có được nhấn hay không và thay đổi giá trị của đèn LED sau mỗi lần nhấn nút. Chúng ta sẽ sử dụng bộ hẹn giờ để nhận được kết quả chính xác hơn:

// read the state of the switch into a local variable:
int reading = digitalRead(buttonPin);

// check to see if you just pressed the button
// (i.e. the input went from LOW to HIGH), and you've waited long enough
// since the last press to ignore any noise:

// If the switch changed, due to noise or pressing:
if (reading != lastButtonState) {
  // reset the debouncing timer
  lastDebounceTime = millis();
}

if ((millis() - lastDebounceTime) > debounceDelay) {
  // whatever the reading is at, it's been there for longer than the debounce
  // delay, so take it as the actual current state:

  // if the button state has changed:
  if (reading != buttonState) {
    buttonState = reading;

    // only toggle the LED if the new button state is HIGH
    if (buttonState == HIGH) {
      ledState = !ledState;
    }
  }
}
// save the reading. Next time through the loop, it'll be the lastButtonState:
lastButtonState = reading;

Việc bạn cần làm là lưu trạng thái đèn LED vào bộ nhớ Flash mỗi khi trạng thái LED thay đổi. Chúng ta có thể kiểm tra trạng thái của cổng GPIO có thay đổi hay không dựa vào biến LEDState:

if (digitalRead(ledPin)!= ledState) {

Nếu điều đó xảy ra, chúng ta sẽ thay đổi trạng thái đèn LED bằng câu lệnh sau:

digitalWrite(ledPin, ledState);

Sau đó, chúng ta lưu trạng thái hiện lại hiện tại vào bộ nhớ Flash bằng EEPROM.write(), với thông tin bao gồm:

  • Địa chỉ (trong trường hợp này là 0)
  • Giá trị cần lưu (trong trường hợp này là biến LEDState)
EEPROM.write(0, ledState);

Cuối cùng, chúng ta sử dụng câu lệnh sau để lưu những thay đổi:

EEPROM.commit();

Kết quả đạt được

Sau khi nạp chương trình vào mạch ESP32, bạn hãy thử nhấn nút để bật và tắt đèn LED. Sau khi reset mạch ESP32 hoặc ngắt khỏi nguồn điện, đèn LED phải giữ trạng thái cuối cùng.

Kết quả sau khi lưu dữ liệu vào bộ nhớ Flash ESP32
Kết quả sau khi lưu dữ liệu vào bộ nhớ Flash ESP32

Lời kết

Trong bài này, IoTZone đã hướng dẫn bạn cách lưu dữ liệu vào bộ nhớ Flash ESP32 bằng thư viện EEPROM. Các dữ liệu này sẽ không bị mất đi, ngay cả khi bạn reset mạch hoặc ngắt mạch khỏi nguồn điện. Chúc các bạn thành công!

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *