赞
踩
本文档深入剖析一段C++代码,该代码运用FreeRTOS实时操作系统结合Arduino框架,构建了一个高效稳定的ESP32环境监测系统。系统不仅实时采集温度和湿度数据,还将这些数据记录到SD卡中,并通过FTP服务器实现远程访问。以下对代码中体现的FreeRTOS特性及与Arduino框架的融合进行详细解读。
本文所需要的硬件与我的另一篇文章完全一样,请参照ESP32环境下基于SD卡与FTP实现温湿度数据采集与存储
代码导入了FreeRTOS相关库(如freertos/timers.h
和freertos/queue.h, 这两个库是ESP32自带的,如果你已经在IDE配置好了ESP32,不用单独安装
),与Arduino库(如Arduino.h
、SPI.h
、DHT.h
、WiFi.h
等)一同支持ESP32的多任务并发执行及硬件设备交互。常量定义涵盖了Wi-Fi网络配置、FTP登录信息、传感器引脚映射、NTP服务器设置等,为系统运行提供必需的基础配置。
全局变量和对象中,除了与具体功能相关的DHT
、FTPServer
对象外,还引入了FreeRTOS的互斥信号量sdCardMutex
和environmentDataMutex
,以实现对共享资源(如SD卡访问和环境数据更新)的线程安全控制。此外,定义了用于存储环境数据的CSVRecordData
结构体和临时存储CSV文件内容的LineBuffer
结构体。
声明的辅助函数充分利用了FreeRTOS提供的线程同步机制,如:
printLocalTime()
:获取并格式化本地时间字符串。handleWiFiConnection()
:采用线程安全的方式建立Wi-Fi连接,包括重试逻辑。initializeSPIAndSD()
:初始化SPI接口与SD卡。initializeSystem()
:初始化整个系统,包括DHT传感器、互斥信号量及SD卡,遵循FreeRTOS编程规范。setupTasks()
:利用FreeRTOS任务调度功能创建并启动环境数据采集、CSV记录写入和文件读取任务。handleFTP()
:设置FTP服务器参数并启动服务,与主任务并发运行。syncTime()
:借助系统定时器同步系统时间至指定NTP服务器。setup()
与循环函数loop()
setup()
函数作为系统初始化入口,遵循Arduino编程风格,执行以下操作:
initializeSystem()
进行系统整体初始化,整合FreeRTOS特性。loop()
函数遵循Arduino框架,仅负责处理FTP请求并监控Wi-Fi连接状态。若Wi-Fi连接断开,触发handleWiFiConnection()
重新连接,保持网络连通性。
initializeSPIAndSD()
初始化SPI总线(指定SCK、MISO、MOSI和CS引脚)并尝试连接SD卡。遵循标准Arduino流程,同时确保与FreeRTOS任务调度的兼容性。连接失败时输出错误信息,成功则输出连接成功的提示。
handleWiFiConnection()
在FreeRTOS环境下尝试连接指定Wi-Fi网络,实现线程安全的重试逻辑。每次尝试之间插入适当延时,并通过.
字符输出连接进度。连接成功后,打印Wi-Fi连接状态和本地IP地址,遵循Arduino的简洁反馈原则。
setupTasks()
利用FreeRTOS的xTaskCreate()
函数创建并启动三个任务:
EnvironmentTask
:作为实时任务周期性采集环境数据(湿度和温度),利用FreeRTOS任务优先级确保数据采集的实时性。WriteCSVRecordTask
:定期检查是否有有效环境数据,若有则通过互斥信号量保护机制将其按照CSV格式写入指定文件(CSV_FILE_PATH
)。任务调度确保数据写入与采集任务互不干扰。ReadFileTask
:同样作为FreeRTOS任务,定期读取CSV文件内容,通过互斥信号量保护暂存并打印最后MAX_LINES
行数据,与其它任务并发执行,提高系统效率。WriteCSVRecordTask()
此FreeRTOS任务在每个周期内检查环境数据的有效性。若数据有效,获取当前本地时间并格式化为字符串,然后锁定SD卡和环境数据互斥信号量,使用FreeRTOS兼容方式打开CSV文件进行追加写入。写入格式为“时间戳,湿度值,温度值”。完成写入后释放互斥信号量,关闭文件,确保操作的原子性。
ReadFileTask()
作为FreeRTOS任务,此函数每30秒读取一次CSV文件。锁定SD卡互斥信号量后,按照Arduino文件操作习惯打开文件并逐行读取,将最后MAX_LINES
行数据暂存在LineBuffer
结构体中。随后释放互斥信号量,打印暂存的行数据,与其它任务并发执行,避免阻塞系统运行。
syncTime()
通过配置FreeRTOS定时器,定时触发系统时间与指定NTP服务器同步,确保系统内部时间的准确性和实时性。
总结而言,本代码充分利用FreeRTOS实时操作系统的优势,结合Arduino编程框架的简洁易用性,构建了一套高效、稳定且功能完备的ESP32环境监测系统。系统不仅实现了温度和湿度数据的实时采集、SD卡数据记录,还通过FTP服务器实现了远程访问,所有功能均在FreeRTOS任务调度下并发执行,确保了系统的高响应速度与任务间互不干扰的并发处理能力。
#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 <FS.h> #include <freertos/timers.h> #include <freertos/queue.h> #include <string> // 常量定义 const char *const SSID = "xxx"; const char *const PASSWORD = "xxxxxxxx"; const int MAX_RETRY_COUNT = 5; int retryCount = 0; const char *const FTP_USER = "ftp"; const char *const FTP_PASSWORD = "ftp"; const int DHTPIN = 13; const int DHTTYPE = DHT22; const int CS = 23; const int MOSI_PIN = 17; const int MISO_PIN = 2; const int SCK_PIN = 16; const char *ntpServer = "ntp1.aliyun.com"; const long gmtOffset_sec = 8 * 3600; const int daylightOffset_sec = 0; const char *const CSV_FILE_PATH = "/record0001.csv"; const size_t MAX_LINES = 3; const size_t MAX_LINE_LENGTH = 1024; // 全局变量和对象 DHT dht(DHTPIN, DHTTYPE); FTPServer ftp; SPIClass CustomSPI; SemaphoreHandle_t sdCardMutex; SemaphoreHandle_t environmentDataMutex; // 数据结构:CSV记录 typedef struct { float humidity; float temperature; } CSVRecordData; // 初始化CSV记录 CSVRecordData record; // 用于存储固定数量的字符串行 struct LineBuffer { std::string lines[MAX_LINES]; // 用于存储字符串行的数组,MAX_LINES定义了最大行数。 size_t currentLineIndex = 0; // 当前操作的行索引,默认为0,表示数组的起始位置。 }; // 辅助函数声明 String printLocalTime(); void handleWiFiConnection(); void initializeSPIAndSD(); void initializeSystem(); void setupTasks(); void EnvironmentTask(void *pvParameters); void WriteCSVRecordTask(void *pvParameters); void ReadFileTask(void *pvParameters); void handleFTP(); void syncTime(); void setup() { Serial.begin(9600); delay(500); initializeSystem(); Serial.println("System initialization complete"); handleWiFiConnection(); Serial.println("WiFi connected successfully!"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); Serial.println("WiFi connection established"); setupTasks(); Serial.println("Task initialization complete"); handleFTP(); Serial.println("FTP server initialization complete"); syncTime(); delay(500); Serial.println("Time synchronization completed"); } void loop() { ftp.handle(); // 处理FTP请求 // 检查WiFi连接状态,未连接则尝试重新连接 if (WiFi.status() != WL_CONNECTED) { handleWiFiConnection(); } } void initializeSPIAndSD() { CustomSPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS); delay(500); 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."); } } void initializeSystem() { dht.begin(); sdCardMutex = xSemaphoreCreateMutex(); // 创建互斥锁以用于SD卡访问控制 environmentDataMutex = xSemaphoreCreateMutex(); // 创建互斥锁以用于环境数据访问控制 initializeSPIAndSD(); // 初始化SPI和SD卡 } void handleWiFiConnection() { Serial.println("Connecting to Wi-Fi..."); WiFi.begin(SSID, PASSWORD); delay(1000); while (WiFi.status() != WL_CONNECTED && retryCount < 5) { delay(500); Serial.print("."); WiFi.begin(SSID, PASSWORD); delay(1000); retryCount++; } } void setupTasks() { // 创建名为"EnvironmentTask"的任务,堆栈大小为2048*4字节,优先级为1,不传递参数。 xTaskCreate(&EnvironmentTask, "EnvironmentTask", 2048 * 4, NULL, 1, NULL); delay(500); // 创建名为"WriteCSVRecordTask"的任务,堆栈大小为2048*4字节,优先级为2,不传递参数。 xTaskCreate(&WriteCSVRecordTask, "WriteCSVRecordTask", 2048 * 4, NULL, 2, NULL); delay(500); // 创建名为"ReadFileTask"的任务,堆栈大小为2048*4字节,优先级为3,不传递参数。 xTaskCreate(&ReadFileTask, "ReadFileTask", 2048 * 4, NULL, 3, NULL); } void WriteCSVRecordTask(void *pvParameters) { File currentFile; const char *currentFilePath = CSV_FILE_PATH; uint32_t fileSizeThreshold = 10 * 1024; // 1 kB threshold while (1) { // 判断温湿度是否有效 if (record.humidity != NAN && record.temperature != NAN) { String localTimeStr = printLocalTime(); char timeStr[30]; strcpy(timeStr, localTimeStr.c_str()); xSemaphoreTake(sdCardMutex, portMAX_DELAY); xSemaphoreTake(environmentDataMutex, portMAX_DELAY); currentFile = SD.open(currentFilePath, FILE_APPEND); if (!currentFile) { Serial.print("Error opening initial file: "); Serial.println(CSV_FILE_PATH); } else { Serial.printf("Writing to %s ", currentFilePath); currentFile.print(timeStr); currentFile.print(","); currentFile.print(record.humidity, 2); currentFile.print(","); currentFile.println(record.temperature, 2); currentFile.close(); Serial.println("completed."); } xSemaphoreGive(sdCardMutex); xSemaphoreGive(environmentDataMutex); } vTaskDelay(pdMS_TO_TICKS(6000)); } } void ReadFileTask(void *pvParameters) { LineBuffer buffer; while (1) { xSemaphoreTake(sdCardMutex, portMAX_DELAY); File currentFile = SD.open(CSV_FILE_PATH); if (currentFile) { Serial.printf("Reading file from %s\n", CSV_FILE_PATH); std::string currentLine; size_t lineCount = 0; while (currentFile.available()) { currentLine = std::string(currentFile.readStringUntil('\n').c_str()); buffer.lines[buffer.currentLineIndex++] = currentLine; buffer.currentLineIndex %= MAX_LINES; // 确保索引在范围内 lineCount++; } currentFile.close(); xSemaphoreGive(sdCardMutex); if (lineCount >= MAX_LINES) { for (size_t i = 0; i < MAX_LINES; ++i) { Serial.println(buffer.lines[(buffer.currentLineIndex - i + MAX_LINES) % MAX_LINES].c_str()); // Cycle print last MAX_LINES rows } } else { for (size_t i = 0; i < lineCount; ++i) { Serial.println(buffer.lines[i % MAX_LINES].c_str()); // Print all content } } Serial.println("Read succeeded"); } else { char error_msg[64]; sprintf(error_msg, "error opening %s", CSV_FILE_PATH); Serial.println(error_msg); } vTaskDelay(pdMS_TO_TICKS(30000)); } } // 环境数据采集定时器回调函数 void EnvironmentTask(void *pvParameters) { while (1) { xSemaphoreTake(environmentDataMutex, portMAX_DELAY); if (!isnan(record.humidity) || !isnan(record.temperature)) { record.humidity = dht.readHumidity(); record.temperature = dht.readTemperature(); } xSemaphoreGive(environmentDataMutex); vTaskDelay(pdMS_TO_TICKS(3000)); } } void syncTime() { configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); } 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; } void handleFTP() { ftp.addUser(FTP_USER, FTP_PASSWORD); // 添加FTP用户 ftp.addFilesystem("SD", &SD); // 注册SD卡文件系统 ftp.begin(); // 启动FTP服务 }
[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
C:\Users\Firmin>ftp 192.168.3.39 连接到 192.168.3.39。 220--- Welcome to FTP Server for ESP32 --- 220--- By Peter Buchegger --- 220 -- Version 0.1 --- 200 Ok 用户(192.168.3.39:(none)): ftp 331 OK. Password required 密码: 230 OK. ftp> cd SD 250 Ok. Current directory is /SD ftp> dir /B 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 4543 Jan 01 1970 record2.csv -rw-r--r-- 1 owner group 14349 Jan 01 1970 record.csv -rw-r--r-- 1 owner group 72469 Jan 01 1970 record0001.csv 226 4 matches total ftp: 收到 279 字节,用时 0.01秒 46.50千字节/秒。 ftp> lcd D:\temp 目前的本地目录 D:\temp。 ftp> recv record0001.csv
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。