忘记过去,超越自己
- ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️
- ❤️ 本篇创建记录 2023-05-29 ❤️
- ❤️ 本篇更新记录 2023-05-29 ❤️
- 🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
- 🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!
- 🔥 Arduino ESP8266教程累计帮助过超过1W+同学入门学习硬件网络编程,入选过选修课程,刊登过无线电杂志 🔥
快速导读
- 1、前言
- 2、esp32_arduino_sqlite3_lib 库
- 3、硬件说明
- 4、API说明
- 4.1 sqlite3_initialize —— 初始化Sqlite数据库引擎
- 4.2 sqlite3_open —— 打开或创建一个 SQLite 数据库文件
- 4.3 sqlite3_exec —— 执行 SQL 命令
- 4.4 sqlite3_free —— 释放内存
- 4.5 sqlite3_close —— 关闭先前打开的数据库连接
- 4.6 sqlite3_errmsg —— 用于获取最近一次 SQLite API 调用产生的错误信息
- 4.7 sqlite3_errcode —— 获取最近一次 SQLite API 调用产生的标准错误码
- 4.8 sqlite3_extended_errcode —— 获取最近一次 SQLite API 调用产生的扩展错误码
- 5、练习
- 5.1 利用SPIFFS运行数据库
- 5.2 利用SD卡运行数据库
- 6、应用场景
1、前言
主要学习
https://github.com/siara-cc/esp32_arduino_sqlite3_lib
以及如何使用sqlite来存储我们的业务数据。
该库支持通过 ESP32 SoC 从 SPIFFS
或 SD
卡访问 SQLite 数据库文件。但是对于我们来说SD卡可能更加合适,毕竟空间大。
注意,这里不教sqlite3的基本语法。以及sqlite为啥在嵌入式这么受欢迎。
Sqlite pager和B-tree的运行机制
2、esp32_arduino_sqlite3_lib 库
Arduino IDE -> 工具 -> 管理库 -> 搜索 Sqlite3Esp32
-> 安装最新版本
除了 Arduino 和 ESP32 核心 SDK 之外,没有依赖项。Sqlite3 代码包含在库中。
注意,此库局限性。
- 没有严重的限制,除了在
大型数据集上有点慢
。从包含 1000 万行的数据集中检索需要大约 700 毫秒,即使使用索引也是如此。但是对于我们来说已经足够。未实现锁定
。所以它不能可靠地用于多线程/多核代码集,只读操作除外。- 任何闪存,例如 SPIFFS 或微型 SD 卡上可用的闪存,都对每个扇区的写入/擦除次数有限制。通常限制为 10000 次写入或 100000 次写入(在同一扇区)。尽管 ESP32 支持磨损均衡,但在进入写入密集型数据库项目之前要牢记这一点。从 Flash 读取没有限制。
3、硬件说明
这里直接选择esp32-cam,因为它直接有SD卡槽,插入一张sd卡即可,完美!!!!如果是其他esp32板子,就得自己连接一下SD卡连线。
4、API说明
4.1 sqlite3_initialize —— 初始化Sqlite数据库引擎
SQLite 数据库引擎的初始化函数。它在使用 SQLite 数据库之前必须被调用,以确保数据库引擎已被正确地初始化。该函数将创建或打开 SQLite 数据库的内部数据结构,设置全局变量和其他必要的初始化步骤。
4.2 sqlite3_open —— 打开或创建一个 SQLite 数据库文件
sqlite3_open
是 SQLite 数据库引擎的一个函数,用于打开或创建一个 SQLite 数据库文件。它的函数原型如下:
int sqlite3_open(
const char *filename, /* 数据库文件名 */
sqlite3 **ppDb /* 指向指针的指针,用于存储打开的数据库句柄 */
);
filename 参数是数据库文件的名称,如果该文件不存在,则会创建一个新的数据库文件。如果 filename 参数为 “:memory:”,则表示创建一个内存数据库。ppDb 参数是一个指向指针的指针,用于存储打开的数据库句柄。如果打开数据库成功,则会将句柄存储在该指针指向的内存中,如果打开失败,则会把指针设置为 NULL。
例如,以下代码段展示了如何使用 sqlite3_open 函数打开一个名为 test.db 的数据库文件:
sqlite3 *db;
int rc = sqlite3_open("test.db", &db);
if (rc != SQLITE_OK) {
// 打开数据库失败
} else {
// 打开数据库成功,可以使用 db 句柄进行后续操作
}
在使用完数据库之后,需要调用 sqlite3_close 函数关闭数据库。
4.3 sqlite3_exec —— 执行 SQL 命令
它的函数原型如下:
int sqlite3_exec(
sqlite3* db, /* 数据库句柄 */
const char* sql, /* SQL 命令 */
int (*callback)(void*,int,char**,char**), /* 回调函数 */
void *data, /* 回调函数的上下文参数 */
char **errmsg /* 错误信息,如果有错误发生 */
);
db
参数是 SQLite 数据库句柄sql
参数是需要执行的 SQL 命令callback
参数是回调函数指针data
参数是回调函数的上下文参数errmsg
参数是一个指向字符指针的指针,用于存储错误信息(如果有错误发生)。
当执行 SQL 命令时,sqlite3_exec 函数会多次调用回调函数,每次调用传递一个结果行的数据。回调函数的函数原型为:
int callback(void* data, int argc, char** argv, char** colName);
其中,data
参数为回调函数的上下文参数,argc
参数为结果行中的列数,argv
参数为指向结果行中每个列的指针数组,colName
参数为指向结果行中每个列的名称的指针数组。
例如,以下代码段展示了如何使用 sqlite3_exec 函数执行一条 SELECT 命令:
sqlite3 *db;
int rc = sqlite3_open("test.db", &db);
if (rc != SQLITE_OK) {
// 打开数据库失败
} else {
char *errmsg;
// SELECT 命令的回调函数
int callback(void *data, int argc, char **argv, char **colName) { for (int i = 0; i < argc; i++) {
printf("%s = %s\n", colName[i], argv[i] ? argv[i] : "NULL");
}
return 0;
}
// 执行 SELECT 命令
rc = sqlite3_exec(db, "SELECT * FROM mytable", callback, 0, &errmsg);
if (rc != SQLITE_OK) {
printf("SELECT failed: %s\n", errmsg);
sqlite3_free(errmsg);
}
sqlite3_close(db);
}
在使用完数据库之后,需要调用 sqlite3_close 函数关闭数据库。
4.4 sqlite3_free —— 释放内存
用于释放先前通过 sqlite3_malloc 或 sqlite3_realloc 分配的内存。它的原型为:
void sqlite3_free(void*);
其中,参数是一个指向要释放的内存块的指针。
使用 sqlite3_free 可以避免内存泄漏的问题,因为它会将之前分配的内存块标记为可用,以便后续的内存分配可以复用这些空间。但是需要注意的是,必须使用相同的内存分配函数(即 sqlite3_malloc 或 sqlite3_realloc)来分配和释放内存块,否则可能会导致内存错误。
4.5 sqlite3_close —— 关闭先前打开的数据库连接
用于关闭先前打开的数据库连接。它的原型为:
int sqlite3_close(sqlite3*);
其中,参数是一个指向已打开的数据库连接的指针。
使用 sqlite3_close 可以释放数据库连接占用的资源,包括内存和文件句柄等。在关闭数据库连接之前,应该先释放所有相关的内存和资源,例如关闭所有已经打开的数据库句柄和清理所有的 prepared statements,以避免资源泄漏。
需要注意的是,当一个数据库连接被关闭后,与该连接相关的所有数据库句柄和 prepared statements 都会变成无效。因此,在调用 sqlite3_close 之后,不应该再使用任何与该连接相关的资源。
4.6 sqlite3_errmsg —— 用于获取最近一次 SQLite API 调用产生的错误信息
它的原型为:
const char *sqlite3_errmsg(sqlite3*);
其中,参数是一个指向已打开的数据库连接的指针。如果最近一次的 SQLite API 调用没有产生错误,那么 sqlite3_errmsg 返回一个空字符串。
当 SQLite API 调用出现错误时,sqlite3_errmsg 可以用来获取错误信息,以便排查问题。例如,假设在执行 SQL 查询时出现错误,可以使用 sqlite3_errmsg 来获取错误信息并输出到控制台:
sqlite3 *db;
sqlite3_open("example.db", &db);
sqlite3_exec(db, "SELECT * FROM users WHERE id = 0;", NULL, NULL, NULL);
fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
4.7 sqlite3_errcode —— 获取最近一次 SQLite API 调用产生的标准错误码
它的原型为:
int sqlite3_errcode(sqlite3*);
其中,参数是一个指向已打开的数据库连接的指针。
sqlite3_errcode 返回的错误码是一个整数,可以与 SQLite 的错误码宏比较,以判断错误的类型。例如,如果 sqlite3_errcode 返回 SQLITE_CONSTRAINT_UNIQUE,那么表示最近一次 SQLite API 调用遇到了一个唯一性约束错误。
需要注意的是,sqlite3_errcode 返回的错误码是一个标准错误码,与 sqlite3_extended_errcode 返回的扩展错误码不同。如果想要同时获取标准错误码和扩展错误码,可以使用 sqlite3_errcode 和 sqlite3_extended_errcode 配合使用。同时需要注意,sqlite3_errcode 只能返回最近一次 SQLite API 调用的错误码,如果想要获取之前的错误码,需要使用 sqlite3_extended_errcode。
4.8 sqlite3_extended_errcode —— 获取最近一次 SQLite API 调用产生的扩展错误码
与 sqlite3_errcode 不同的是,sqlite3_extended_errcode 提供了更加详细的错误信息,可以用于更好地排查问题。它的原型为:
int sqlite3_extended_errcode(sqlite3*);
其中,参数是一个指向已打开的数据库连接的指针。
sqlite3_extended_errcode 返回的错误码是一个整数,可以与 SQLite 的错误码宏比较,以判断错误的类型。例如,如果 sqlite3_extended_errcode 返回 SQLITE_CONSTRAINT_FOREIGNKEY,那么表示最近一次 SQLite API 调用遇到了一个外键约束错误。
需要注意的是,sqlite3_extended_errcode 返回的错误码是一个扩展错误码,与 sqlite3_errcode 返回的标准错误码不同。如果想要获取标准错误码,可以使用 sqlite3_errcode。如果想要同时获取标准错误码和扩展错误码,可以使用 sqlite3_errcode 和 sqlite3_extended_errcode 配合使用。
5、练习
接下来会通过几个练习来学习一下sqlite3的具体使用场景和用法。
5.1 利用SPIFFS运行数据库
/*
This creates two empty databases, populates values, and retrieves them back
from the SPIFFS file
创建两个空数据库,用来测试简单CURD操作
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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) {
Serial.print(F("DB Name:"));
Serial.print(filename);
Serial.println(F(""));
if (db != NULL)
sqlite3_close(*db);
int rc = sqlite3_open(filename, db);
if (rc) {
Serial.print(F("Can't open database: "));
Serial.print(sqlite3_extended_errcode(*db));
Serial.print(" ");
// 打印错误信息
Serial.println(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.print(F("SQL error: "));
Serial.print(sqlite3_extended_errcode(db));
Serial.print(" ");
Serial.println(zErrMsg);
sqlite3_free(zErrMsg);
} else {
Serial.printf("Operation done successfully\n");
}
// 计算花费时间
Serial.print(F("Time taken:"));
Serial.print(micros()-start);
Serial.println(F(" us"));
return rc;
}
void setup() {
Serial.begin(115200);
// 创建两个数据库对象
sqlite3 *db1 = NULL;
sqlite3 *db2 = NULL;
int rc;
// 初始化SPIFFS文件系统
if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) {
Serial.println("Failed to mount file system");
return;
}
// 获取根目录
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();
}
// 移除已经存在的db文件
SPIFFS.remove("/test1.db");
SPIFFS.remove("/test2.db");
// 初始化sqlite3
sqlite3_initialize();
// 打开数据库
if (db_open("/spiffs/test1.db", &db1))
return;
if (db_open("/spiffs/test2.db", &db2))
return;
// 执行数据库db1建表语句
rc = db_exec(db1, "CREATE TABLE test1 (id INTEGER, content);");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db2建表语句
rc = db_exec(db2, "CREATE TABLE test2 (id INTEGER, content);");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db1插入语句
rc = db_exec(db1, "INSERT INTO test1 VALUES (1, 'Hello, World from test1');");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db2插入语句
rc = db_exec(db2, "INSERT INTO test2 VALUES (1, 'Hello, World from test2');");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db1查询语句
rc = db_exec(db1, "SELECT * FROM test1");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db2查询语句
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() {
}
- 创建数据表时间花费为 220ms左右
- 插入一条数据时间花费为130ms左右
- 查询操作,10ms左右
5.2 利用SD卡运行数据库
/*
在sd卡里面操作数据库
*/
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <SPI.h>
//#include <FS.h>
#include "SD_MMC.h"
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) {
Serial.print(F("DB Name:"));
Serial.print(filename);
Serial.println(F(""));
if (db != NULL)
sqlite3_close(*db);
int rc = sqlite3_open(filename, db);
if (rc) {
Serial.print(F("Can't open database: "));
Serial.print(sqlite3_extended_errcode(*db));
Serial.print(" ");
// 打印错误信息
Serial.println(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.print(F("----------------------- db_exec -----------------------\n"));
Serial.println(sql);
Serial.println("");
// 记录一个开始执行时间
long start = micros();
int rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
if (rc != SQLITE_OK) {
Serial.print(F("SQL error: "));
Serial.print(sqlite3_extended_errcode(db));
Serial.print(" ");
Serial.println(zErrMsg);
sqlite3_free(zErrMsg);
} else {
Serial.printf("Operation done successfully\n");
}
// 计算花费时间
Serial.print(F("Time taken:"));
Serial.print(micros()-start);
Serial.println(F(" us"));
Serial.println(F("----------------------- db_exec -----------------------\n"));
return rc;
}
void setup() {
Serial.begin(115200);
sqlite3 *db1;
sqlite3 *db2;
char *zErrMsg = 0;
int rc;
SPI.begin();
SD_MMC.begin();
sqlite3_initialize();
// 打开数据库
if (db_open("/sdcard/test1.db", &db1))
return;
if (db_open("/sdcard/test2.db", &db2))
return;
// 执行数据库db删表语句
rc = db_exec(db1, "DROP TABLE IF EXISTS test1;");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db删表语句
rc = db_exec(db2, "DROP TABLE IF EXISTS test2;");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db1建表语句
rc = db_exec(db1, "CREATE TABLE IF NOT EXISTS test1 (id INTEGER, content);");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db2建表语句
rc = db_exec(db2, "CREATE TABLE IF NOT EXISTS test2 (id INTEGER, content);");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db1插入语句
rc = db_exec(db1, "INSERT INTO test1 VALUES (1, 'Hello, World from test1');");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db2插入语句
rc = db_exec(db2, "INSERT INTO test2 VALUES (1, 'Hello, World from test2');");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db1查询语句
rc = db_exec(db1, "SELECT * FROM test1");
if (rc != SQLITE_OK) {
// 执行失败就关闭数据库
sqlite3_close(db1);
sqlite3_close(db2);
return;
}
// 执行数据库db2查询语句
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() {
}
操作test1.db 和 test2.db
串口日志内容:
DB Name:/sdcard/test1.db
Opened database successfully
DB Name:/sdcard/test2.db
Opened database successfully
----------------------- db_exec -----------------------
DROP TABLE IF EXISTS test1;
Operation done successfully
Time taken:26229 us
----------------------- db_exec -----------------------
----------------------- db_exec -----------------------
DROP TABLE IF EXISTS test2;
Operation done successfully
Time taken:15856 us
----------------------- db_exec -----------------------
----------------------- db_exec -----------------------
CREATE TABLE IF NOT EXISTS test1 (id INTEGER, content);
Operation done successfully
Time taken:99951 us
----------------------- db_exec -----------------------
----------------------- db_exec -----------------------
CREATE TABLE IF NOT EXISTS test2 (id INTEGER, content);
Operation done successfully
Time taken:71794 us
----------------------- db_exec -----------------------
----------------------- db_exec -----------------------
INSERT INTO test1 VALUES (1, 'Hello, World from test1');
Operation done successfully
Time taken:44203 us
----------------------- db_exec -----------------------
----------------------- db_exec -----------------------
INSERT INTO test2 VALUES (1, 'Hello, World from test2');
Operation done successfully
Time taken:36820 us
----------------------- db_exec -----------------------
----------------------- db_exec -----------------------
SELECT * FROM test1
Callback function called: id = 1
content = Hello, World from test1
Operation done successfully
Time taken:12690 us
----------------------- db_exec -----------------------
----------------------- db_exec -----------------------
SELECT * FROM test2
Callback function called: id = 1
content = Hello, World from test2
Operation done successfully
Time taken:12130 us
----------------------- db_exec -----------------------
6、应用场景
整体下来,个人建议可以先在电脑上创建好db文件(提前创建好),然后利用一个小内存的sd卡(比如几百MB等等),这样就可以存储大量的数据了。
- 温湿度环境数据
- 打卡数据
- 远程环境监测
- 等等