本篇文章将介绍如何利用ESP32开发板结合SD卡与FTP服务器功能,实现温湿度数据的实时采集、存储与远程访问。项目代码基于Arduino IDE平台编写,主要依赖于以下库:
SPI
DHT
WiFi
time.h
ESP-FTP-Server-Lib
SD
SPIFFS
FS
1. 硬件连接与配置
1.1 所需要的硬件
-
ESP32开发板
-
DHT22传感器模块
-
SD卡模块
1.2 SD卡接口
ESP32通过SPI总线与SD卡进行通信,各引脚连接如下:
- CS:GPIO 23
- MOSI:GPIO 17
- MISO:GPIO 2
- SCK:GPIO 16
1.3 DHT22温湿度传感器
DHT22传感器连接至ESP32的GPIO 13引脚。
1.4 Wi-Fi模块
ESP32内置Wi-Fi模块,用于接入无线网络。此处设定的Wi-Fi信息如下:
- SSID:
xxx
- Password:
xxx
1.5 NTP服务器与时区设置
为了获取准确的本地时间,通过NTP协议从阿里云NTP服务器同步时间,并设定时区为中国标准时间(UTC+8):
- NTP Server:
ntp1.aliyun.com
- GMT Offset:
8 * 3600
秒 - Daylight Offset:
0
秒
1.6 FTP服务器配置
创建一个FTP服务器,供远程设备访问存储在ESP32上的温湿度数据。FTP登录信息如下:
- Username:
ftp
- Password:
ftp
2. 代码详解
2.1 引入库与定义常量
#include <Arduino.h>
/*
sd Card Interface code for ESP32
SPI Pins of ESP32 sd card as follows:
CS = 23;
MOSI = 17;
MISO = 2;
SCK = 16;
*/
#include <SPI.h>
#include <DHT.h>
#include <WiFi.h>
#include <time.h>
#include <ESP-FTP-Server-Lib.h>
#include <SD.h>
#include <SPIFFS.h>
#include <FS.h>
#define DHTPIN 13
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
static float h = 0;
static float t = 0;
File myFile;
FTPServer ftp;
// 设置SPI Pins
const int CS = 23;
const int MOSI_PIN = 17;
const int MISO_PIN = 2;
const int SCK_PIN = 16;
SPIClass CustomSPI;
// 设置wifi信息,更换为你的wifi设置
const char *ssid = "xxx";
const char *password = "xxx";
// NTP相关参数,设置NTP服务器为阿里NTP服务器
const char *ntpServer = "ntp1.aliyun.com";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
char timeStr_bj[9];
char dateStr_bj[11];
char timeStr_gmt[9];
// FTP服务器配置
#define FTP_USER "ftp"
#define FTP_PASSWORD "ftp"
// 上一次执行的时间,初始化为0
unsigned long previousExecutionTime = 0;
// 执行间隔时间,单位为毫秒,这里设置为60000毫秒,即1分钟
const unsigned long INTERVAL_MS = 60000;
String printLocalTime()
{
struct tm timeinfo;
if (!getLocalTime(&timeinfo))
{
Serial.println("Failed to obtain time");
return "";
}
char formattedTimeBuffer[30]; // 为格式化的时间字符串分配足够的缓冲区
strftime(formattedTimeBuffer, sizeof(formattedTimeBuffer), "%Y-%B-%d %H:%M:%S", &timeinfo); // 使用 strftime 将 timeinfo 转换为格式化的字符串
String formattedTime = formattedTimeBuffer; // 将格式化后的字符串复制到 String 对象中
return formattedTime;
}
/**
* 将温度和湿度数据写入指定路径的CSV文件中。
*
* @param path 指定的CSV文件路径。
* @param timeStr 记录的时间字符串。
* @param humidity 湿度值。
* @param temperature 温度值。
*
* 函数首先尝试打开文件以追加模式写入。如果文件成功打开,将依次写入时间字符串、湿度和温度值,
* 每个数据项之间用逗号分隔,并在温度值后换行。写入完成后,关闭文件并打印完成信息。
* 如果打开文件时出现错误,将打印错误信息和文件路径。
*/
void WriteCSVRecord(const char *path, const char *timeStr, float humidity, float temperature)
{
myFile = SD.open(path, FILE_APPEND);
if (myFile)
{
Serial.printf("Writing to %s ", path);
myFile.print(timeStr);
myFile.print(",");
myFile.print(humidity, 2);
myFile.print(",");
myFile.println(temperature, 2);
myFile.close();
Serial.println("completed.");
}
else
{
Serial.println("error opening file ");
Serial.println(path);
}
}
void ReadFile(const char *path)
{
myFile = SD.open(path);
if (myFile)
{
Serial.printf("Reading file from %s\n", path);
while (myFile.available())
{
// Serial.write(myFile.read());
Serial.println("Read succeded");
}
myFile.close();
}
else
{
char error_msg[64];
sprintf(error_msg, "error opening %s", path);
Serial.println(error_msg);
}
}
void environment()
{
h = dht.readHumidity();
t = dht.readTemperature();
Serial.printf("Humidity: %f \n", h);
Serial.printf("Temperature: %f \n", t);
}
void setup()
{
Serial.begin(9600);
delay(500);
dht.begin();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP()); // 记下运行时生成的IP地址,后面会用到
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
SPIFFS.begin(true);
CustomSPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS);
while (!Serial)
{
;
}
Serial.println("Initializing sd card...");
if (!SD.begin(CS, CustomSPI, 1000000))
{
Serial.println("initialization failed!");
return;
}
else
{
Serial.println("SD card initialization succeeded.");
}
// // 配置FTP
ftp.addUser(FTP_USER, FTP_PASSWORD); // 添加FTP用户
ftp.addFilesystem("SD", &SD); // 注册SD卡文件系统
ftp.addFilesystem("SPIFFS", &SPIFFS); // 注册SPIFFS文件系统
ftp.begin(); // 启动FTP服务
Serial.println("FTP server started"); // 打印启动信息}
void loop()
{
unsigned long currentMillis = millis(); // 获取当前毫秒时间戳
// 检查是否已过去一分钟
if (currentMillis - previousExecutionTime >= INTERVAL_MS)
{
previousExecutionTime = currentMillis; // 更新上一次执行的时间戳
environment(); // 执行环境相关操作
String localTimeStr = printLocalTime(); // 获取本地时间字符串
// 如果温度和湿度都不为零,则写入CSV文件
if (h != 0 && t != 0)
{
WriteCSVRecord("/record.csv", localTimeStr.c_str(), h, t);
}
}
// 处理FTP请求
ftp.handle();
}
2.2 如果你是使用的PlatfromIO IDE可以把以下内容复制到你的platformio.ini里
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 9600
lib_deps =
adafruit/DHT sensor library@^1.4.6
adafruit/Adafruit Unified Sensor@^1.1.14
peterus/ESP-FTP-Server-Lib@^0.14.0
3. FTP命令行操作
3.1 步骤说明
-
打开命令提示符(Windows)或终端(macOS/Linux)。
-
输入以下命令连接到 FTP 服务器(请将 IP 地址替换为实际 FTP 服务器的 IP 地址):
-
输入用户名(默认为 “ftp”)和密码(默认为 “ftp”)。
-
输入 “ls” 或 “dir” 查看文件列表。
-
输入 “cd” 命令切换目录,例如 “cd /SD” 进入 “/SD” 目录。
-
输入 “lcd” 命令切换本地目录,例如 “lcd E:\temp” 进入 “E:\temp” 目录。
-
使用 “get” 或 “recv” 命令从 FTP 服务器下载文件,例如 “get record.csv” 下载 “record.csv” 文件。
-
使用 “put” 或 “send” 命令将文件上传到 FTP 服务器,例如 “put local_file.txt” 上传 “local_file.txt” 文件。
-
输入 “bye” 或 “quit” 命令退出 FTP 客户端。
10.更多命令直接输入"help"来了解
3.2 我用如下步骤下载了SD卡中的record.csv到电脑上:
C:\Users\User>ftp 192.168.55.203
连接到 192.168.55.203。
220--- Welcome to FTP Server for ESP32 ---
220--- By Peter Buchegger ---
220 -- Version 0.1 ---
200 Ok
用户(192.168.55.203:(none)): ftp
331 OK. Password required
密码:
230 OK.
ftp> cd SD
250 Ok. Current directory is /SD
ftp> lcd "E:\temp"
目前的本地目录 E:\temp。
ftp> dir
200 PORT command successful
150 Accepted data connection
drwxr-xr-x 1 owner group 0 Jan 01 1970 System Volume Information
-rw-r--r-- 1 owner group 16 Jan 01 1970 test.txt
-rw-r--r-- 1 owner group 113466 Jan 01 1970 record.csv
226 3 matches total
ftp: 收到 208 字节,用时 0.01秒 20.80千字节/秒。
ftp> recv record.csv
200 PORT command successful
150 Accepted data connection
226 File successfully transferred
ftp: 收到 113466 字节,用时 1.17秒 96.81千字节/秒。
ftp>