单片机/MCU论坛
直播中

HonestQiao

8年用户 520经验值
擅长:嵌入式技术
私信 关注

【FireBeetle 2 ESP32-S3开发板体验】在Arduino中使用基于SPIFFS分区的sqlite3嵌入式数据库

在前一篇文章 【FireBeetle 2 ESP32-S3开发板体验】在Arduino中充分利用FireBeetle 2 ESP32-S3的16MB Flash做SPIFFS 中,分享了在FireBeetle 2 ESP32-S3开发板上使用SPIFFS分区,接下来,继续分享在Arduino中使用基于SPIFFS分区的sqlite3。

sqlite是一个非常小巧的支持SQL语言的嵌入式数据库,这里的嵌入式,有两重意义。

  • 其一,sqlite可以很轻松的,嵌入到其他程序中提供SQL数据库功能,而无需独立的数据库服务端。
  • 其二,sqlite占用资源非常低,使用c编写,在嵌入式设备中应用非常方便,广泛应用于嵌入式物联网领域。

sqlite的每个数据库存储在单个存储文件中,底层的存储基于B-tree,所以在小型数据库中,数据的查找、删除、添加速度非常之快。

sqlite的最新版本为sqlite3,要在Arduino-ESP32S3中使用sqlite,使用esp32_arduino_sqlite3_lib即可。

一、安装sqlite3扩展库

在上一篇文章中说过,Arduino IDE安装后,通常有3个目录分别为:

  • Arduino IDE 程序目录
  • 开发板支持包目录
  • 扩展库目录
    我使用的是macOS系统,所以以上的目录分别为:
    • 开发工具目录:/Applications/Arduino.app
    • 开发板支持目录:/Users/HonestQiao/Library/Arduino15
    • 扩展库目录:/Users/HonestQiao/Documents/Arduino/libraries
    • 插件目录:/Users/HonestQiao/Documents/Arduino/tools

如果是在Windows系统,通常目录如下:

  • 开发工具目录:C:\Program Files (x86)\Arduino
  • 开发板支持目录:C:\Users\Administrator\AppData\Local\Arduino15
  • 扩展库目录:C:\Users\Administrator\Documents\Arduino\libraries
  • 插件目录:C:\Users\Administrator\Documents\Arduino\tools

具体目录,需要根据实际情况确定,上面只是通常的情况。

安装sqlite3扩展库,可以用如下方法:

安装完成后,重启Arduino IDE,才文件菜单的示例中,可以看到对应的例子:

image.png

二、示例测试

在上述sqlite3扩展库的示例中,就包含了基于spiffs的测试,直接选择sqlite3_spiffs即可,示例代码如下:

/*
    This creates two empty databases, populates values, and retrieves them back
    from the SPIFFS file 
*/
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <SPI.h>
#include <FS.h>
#include "SPIFFS.h"

/* You only need to format SPIFFS the first time you run a
   test or else use the SPIFFS plugin to create a partition
   https://github.com/me-no-dev/arduino-esp32fs-plugin */
#define FORMAT_SPIFFS_IF_FAILED true

const char* data = "Callback function called";
static int callback(void *data, int argc, char **argv, char **azColName) {
   int i;
   Serial.printf("%s: ", (const char*)data);
   for (i = 0; i<argc; i++){
       Serial.printf("%s = %s\\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   Serial.printf("\\n");
   return 0;
}

int db_open(const char *filename, sqlite3 **db) {
   int rc = sqlite3_open(filename, db);
   if (rc) {
       Serial.printf("Can't open database: %s\\n", sqlite3_errmsg(*db));
       return rc;
   } else {
       Serial.printf("Opened database successfully\\n");
   }
   return rc;
}

char *zErrMsg = 0;
int db_exec(sqlite3 *db, const char *sql) {
   Serial.println(sql);
   long start = micros();
   int rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
   if (rc != SQLITE_OK) {
       Serial.printf("SQL error: %s\\n", zErrMsg);
       sqlite3_free(zErrMsg);
   } else {
       Serial.printf("Operation done successfully\\n");
   }
   Serial.print(F("Time taken:"));
   Serial.println(micros()-start);
   return rc;
}

void setup() {

   Serial.begin(115200);
   sqlite3 *db1;
   sqlite3 *db2;
   int rc;

   if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) {
       Serial.println("Failed to mount file system");
       return;
   }

   // list SPIFFS contents
   File root = SPIFFS.open("/");
   if (!root) {
       Serial.println("- failed to open directory");
       return;
   }
   if (!root.isDirectory()) {
       Serial.println(" - not a directory");
       return;
   }
   File file = root.openNextFile();
   while (file) {
       if (file.isDirectory()) {
           Serial.print("  DIR : ");
           Serial.println(file.name());
       } else {
           Serial.print("  FILE: ");
           Serial.print(file.name());
           Serial.print("\\tSIZE: ");
           Serial.println(file.size());
       }
       file = root.openNextFile();
   }

   // remove existing file
   SPIFFS.remove("/test1.db");
   SPIFFS.remove("/test2.db");

   sqlite3_initialize();

   if (db_open("/spiffs/test1.db", &db1))
       return;
   if (db_open("/spiffs/test2.db", &db2))
       return;

   rc = db_exec(db1, "CREATE TABLE test1 (id INTEGER, content);");
   if (rc != SQLITE_OK) {
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   rc = db_exec(db2, "CREATE TABLE test2 (id INTEGER, content);");
   if (rc != SQLITE_OK) {
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }

   rc = db_exec(db1, "INSERT INTO test1 VALUES (1, 'Hello, World from test1');");
   if (rc != SQLITE_OK) {
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   rc = db_exec(db2, "INSERT INTO test2 VALUES (1, 'Hello, World from test2');");
   if (rc != SQLITE_OK) {
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }

   rc = db_exec(db1, "SELECT * FROM test1");
   if (rc != SQLITE_OK) {
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   rc = db_exec(db2, "SELECT * FROM test2");
   if (rc != SQLITE_OK) {
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }

   sqlite3_close(db1);
   sqlite3_close(db2);

}

void loop() {
}

上述代码演示了简单的打开sqlite3数据库,新建数据表,新增数据,以及查询数据的方式。

编译代码下到到FireBeetle 2 ESP32-S3开发板运行后,输出如下:
image.png

三、使用sqlite3进行日志记录

完成上一步,就可以在FireBeetle 2 ESP32-S3开发板上顺利使用sqlite3数据库了。

下面,我们再使用sqlite3,做一个简单的日志记录的功能。

首先,我们定义一个log数据表:

sqlite> .schema log
CREATE TABLE log (id INTEGER PRIMARY KEY, info TEXT);
sqlite>

这个表有两个字段,其中主键id为自增字段,info用于存储信息。

然后使用下面的代码进行测试:

/*
 * esp32_arduino_sqlite3_lib
 * https://github.com/siara-cc/esp32_arduino_sqlite3_lib
 * 
  */

#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <SPI.h>
#include <FS.h>
#include "SPIFFS.h"

sqlite3 *db1;
int rc;
int rc_count;
sqlite3_stmt *res;
const char *tail;
String sql;

const char* data = "Callback function called";
static int callback(void *data, int argc, char **argv, char **azColName) {
  int i;
  Serial.printf("%s: ", (const char*)data);
  for (i = 0; i < argc; i++) {
    Serial.printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
  }
  Serial.printf("\n");
  return 0;
}

int db_open(const char *filename, sqlite3 **db) {
  int rc = sqlite3_open(filename, db);
  if (rc) {
    Serial.printf("Can't open database: %s\n", sqlite3_errmsg(*db));
    return rc;
  } else {
    Serial.printf("Opened database successfully\n");
  }
  return rc;
}

char *zErrMsg = 0;
int db_exec(sqlite3 *db, const char *sql) {
  Serial.println(sql);
  long start = micros();
  int rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
  if (rc != SQLITE_OK) {
    Serial.printf("SQL error: %s\n", zErrMsg);
    sqlite3_free(zErrMsg);
  } else {
    Serial.printf("Operation done successfully\n");
  }
  Serial.print(F("Time taken:"));
  Serial.println(micros() - start);
  return rc;
}

void setup() {

  Serial.begin(115200);
  randomSeed(analogRead(A0));

  if (!SPIFFS.begin(true)) {
    Serial.println("Failed to mount file system");
    return;
  }

  // list SPIFFS contents
  File root = SPIFFS.open("/");
  if (!root) {
    Serial.println("- failed to open directory");
    return;
  }
  if (!root.isDirectory()) {
    Serial.println(" - not a directory");
    return;
  }
  File file = root.openNextFile();
  while (file) {
    if (file.isDirectory()) {
      Serial.print("  DIR : ");
      Serial.println(file.name());
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("\tSIZE: ");
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }

  // SPIFFS.remove("/log.db");
  // SPIFFS.remove("log.db-journal");

  sqlite3_initialize();

  if (db_open("/spiffs/log.db", &db1))
    return;

  Serial.println("Check table log");
  rc = db_exec(db1, "SELECT 1 FROM log LIMIT 1");
  if (rc != SQLITE_OK) {
    // 数据表不存在,需要创建
    Serial.println("Create table log");
    rc = db_exec(db1, "CREATE TABLE log (id INTEGER PRIMARY KEY, info TEXT)");
    if (rc != SQLITE_OK) {
      // 创建失败
      Serial.println("Create table failed");
      sqlite3_close(db1);
      return;
    }

    String sql = "INSERT INTO log (info) VALUES(";
    sql += "'frist log'";
    sql += ");";
    db_exec(db1, sql.c_str());
  }

  Serial.println("Count table log");
  sql = "SELECT COUNT(*) FROM log";
  rc = sqlite3_prepare_v2(db1, sql.c_str(), -1, &res, &tail);
  if (rc != SQLITE_OK) {
    sqlite3_close(db1);
    return;
  }

  rc_count = 0;
  while (sqlite3_step(res) == SQLITE_ROW) {
    rc_count = sqlite3_column_int(res, 0);
    Serial.print("Current log record lines: ");
    Serial.println(rc_count);
  }
  sqlite3_finalize(res);

  Serial.print("Check max id: ");
  sql = "SELECT MAX(id) FROM log";
  rc = sqlite3_prepare_v2(db1, sql.c_str(), -1, &res, &tail);
  if (rc != SQLITE_OK) {
    sqlite3_close(db1);
    return;
  }
  int id_max = 0;
  while (sqlite3_step(res) == SQLITE_ROW) {
    id_max = sqlite3_column_int(res, 0);
    Serial.print("Max id: ");
    Serial.println(id_max);
  }
  sqlite3_finalize(res);

  if(rc_count>100) {
    String sql = "DELETE FROM log WHERE id<=";
    sql += id_max-100;
    db_exec(db1, sql.c_str());
  }

  Serial.println("Last 20 logs: ");
  sql = "SELECT * FROM log ORDER BY id DESC LIMIT 20";
  rc = sqlite3_prepare_v2(db1, sql.c_str(), -1, &res, &tail);
  if (rc != SQLITE_OK) {
    sqlite3_close(db1);
    return;
  }

  while (sqlite3_step(res) == SQLITE_ROW) {
    Serial.print(sqlite3_column_int(res, 0));
    Serial.print(", ");
    Serial.println((const char *) sqlite3_column_text(res, 1));
  }
  sqlite3_finalize(res);

}

void loop() {
  delay(10000);
  String sql = "INSERT INTO log (info) VALUES(";
  sql += "'test log, random=";
  sql += random(10000);
  sql += "')";

  db_exec(db1, sql.c_str());
}

上述代码应用了sqlite3_spiffs示例代码的部分,添加了日志数据库处理的部分,具体逻辑如下:

  • 日志数据库为SPIFFS分区下的log.db
  • 通过db_exec()执行SQL查询:SELECT 1 FROM log LIMIT 1
    • 如果查询成功,说明日志数据表已经创建;
    • 如果查询失败,则创建日志数据表,并使用db_exec()执行插入一条"frist log"日志
  • 然后统计日志的条数,以及获取日志数据的最大id值
    • 如果日志条数大于100条,则自动删除旧的日志,仅保留最后100条
  • 取出最新20条日志显示
  • 循环loop中的处理:
  • 每隔10秒,新增一条日志(含随机数)

以上代码每次重启后,都会清理旧的日志;实际上,我们也可以在loop()循环中,适当的时候检查日志条数,及时清理。

编译代码下载,第二启动后,将会出现如下信息:
image.png

从上图可以看到,我们的日志成功写入了,并且输出了之前保存的最后的日志信息。

另外,在上述代码中,使用了随机数,具体调用包括:

  • randomSeed(analogRead(A0)):重置随机数生成器,用A0的输入值作为种子
  • random(10000):在0~10000生成随机整数

四、总结
以上的分享,只是sqlite3在FireBeetle 2 ESP32-S3的简单使用,在实际使用中,可以有很多用法。例如,你可以用它来记录传感器的数据,进行进一步的分析。

想要进一步了解使用sqlite的相关信息,可以查看下面的链接:
siara-cc/esp32_arduino_sqlite3_lib: Sqlite3 Arduino library for ESP32 (github.com)
SQLite 教程 | 菜鸟教程 (runoob.com)

更多回帖

发帖
×
20
完善资料,
赚取积分