#include #include #include #include #include #include #include "esp_spiffs.h" #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "esp_chip_info.h" #include "esp_flash.h" #include "esp_system.h" #include "esp_timer.h" #include "driver/gpio.h" #define PRINT_DEBUG false #define DHT11_GPIO 0 #define BUTTON_PIN 9 // BOOT button on ESP32-C3 SuperMini #define DATA_BUFFER_SIZE 100 #define MAX_MEASUREMENTS_PER_DAY 96 // 96 * 15 minutes = 24 hours static int measurement_count = 0; QueueHandle_t sensor_data_queue; SemaphoreHandle_t xMutex; typedef struct { char timestamp[64]; float humidity; float temperature; } SensorData; TaskHandle_t file_handler_task_handle; // ############### Base Setup ############### void set_system_time_from_compiler() { // Parse __DATE__: "Mmm dd yyyy" char date[] = __DATE__; char time[] = __TIME__; struct tm tm = {0}; char month[4]; int day, year; int hour, min, sec; // Parse date sscanf(date, "%3s %d %d", month, &day, &year); // Parse time sscanf(time, "%d:%d:%d", &hour, &min, &sec); // Convert month abbreviation to number const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; for (int i = 0; i < 12; i++) { if (strcmp(month, months[i]) == 0) { tm.tm_mon = i; break; } } tm.tm_mday = day; tm.tm_year = year - 1900; // Years since 1900 tm.tm_hour = hour; tm.tm_min = min; tm.tm_sec = sec; tm.tm_isdst = -1; // Let system determine daylight saving time // Set system time struct timeval tv = { .tv_sec = mktime(&tm), .tv_usec = 0 }; settimeofday(&tv, NULL); } void print_chip_info(){ /* Print chip information */ #if PRINT_DEBUG esp_chip_info_t chip_info; uint32_t flash_size; esp_chip_info(&chip_info); printf("This is %s chip with %d CPU core(s), %s%s%s%s, ", CONFIG_IDF_TARGET, chip_info.cores, (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "", (chip_info.features & CHIP_FEATURE_BT) ? "BT" : "", (chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "", (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : ""); unsigned major_rev = chip_info.revision / 100; unsigned minor_rev = chip_info.revision % 100; printf("silicon revision v%d.%d, ", major_rev, minor_rev); if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) { printf("Get flash size failed"); return; } printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024), (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size()); #endif } // ############### Measurement task ############### void get_current_time(char* time_string, size_t size) { time_t now; struct tm timeinfo; time(&now); localtime_r(&now, &timeinfo); strftime(time_string, size, "%Y-%m-%d %H:%M:%S", &timeinfo); } void read_dht11(float *humidity, float *temperature) { uint8_t data[5] = {0}; // Send start signal: pull the line low for 20ms gpio_set_direction(DHT11_GPIO, GPIO_MODE_OUTPUT); gpio_set_level(DHT11_GPIO, 0); vTaskDelay(pdMS_TO_TICKS(20)); // Release the line and wait for sensor response gpio_set_level(DHT11_GPIO, 1); gpio_set_direction(DHT11_GPIO, GPIO_MODE_INPUT); // Wait for the sensor to pull the line low (~20-40us) while (gpio_get_level(DHT11_GPIO) == 1) { vTaskDelay(pdMS_TO_TICKS(0.1)); } // Wait for the sensor to pull the line high (~80us) while (gpio_get_level(DHT11_GPIO) == 0) { vTaskDelay(pdMS_TO_TICKS(0.1)); } // Read 40 bits of data for (int i = 0; i < 40; i++) { // Wait for the start of the bit (low for ~50us) while (gpio_get_level(DHT11_GPIO) == 1) { vTaskDelay(pdMS_TO_TICKS(0.1)); } while (gpio_get_level(DHT11_GPIO) == 0) { vTaskDelay(pdMS_TO_TICKS(0.1)); } // Measure the length of the high pulse uint32_t pulse_start = esp_timer_get_time(); while (gpio_get_level(DHT11_GPIO) == 1) { vTaskDelay(pdMS_TO_TICKS(0.1)); } uint32_t pulse_end = esp_timer_get_time(); // Determine if the bit is 0 or 1 uint8_t bit = (pulse_end - pulse_start) > 50 ? 1 : 0; data[i / 8] <<= 1; data[i / 8] |= bit; } // Parse the data uint8_t humidity_int = data[0]; uint8_t humidity_dec = data[1]; // Decimal part (0-9) int8_t temperature_int = (data[2]); // signed value uint8_t temperature_dec = data[3]; // Decimal part (0-9) uint8_t checksum = data[4]; #if PRINT_DEBUG printf("\nReadout values: [hi: %d, hd: %d, ti %d, td %d, c: %d]\n", humidity_int, humidity_dec, temperature_int, temperature_dec, checksum); #endif // Validate checksum uint8_t sum = humidity_int + humidity_dec + temperature_int + temperature_dec; if (sum != checksum) { #if PRINT_DEBUG printf("Checksum failed! Retrying...\n"); #endif vTaskDelay(pdMS_TO_TICKS(1000)); read_dht11(humidity, temperature); } #if PRINT_DEBUG printf("Checksum checks out!\n"); #endif // Calculate humidity and temperature *humidity = (float)humidity_int + (float)humidity_dec / 10.0f; *temperature = (float)temperature_int + (float)temperature_dec / 10.0f; } void measure_task(void *pvParameters){ while (1) { float humidity, temperature; read_dht11(&humidity, &temperature); char time_string[64] = {0}; get_current_time(time_string, sizeof(time_string)); #if PRINT_DEBUG printf("Measurment: [%s, %.1f%%, %.1f°C]\n", time_string, humidity, temperature); #endif SensorData data; strncpy(data.timestamp, time_string, sizeof(data.timestamp)); data.humidity = humidity; data.temperature = temperature; // Send data to the queue if (xQueueSend(sensor_data_queue, &data, pdMS_TO_TICKS(100)) != pdPASS) { #if PRINT_DEBUG printf("Failed to add measurement to queue!\n"); #endif } else { measurement_count++; if (measurement_count >= MAX_MEASUREMENTS_PER_DAY) { // Signal the file handler to write and reset xTaskNotifyGive(file_handler_task_handle); } } // Wait 15 minute before next read vTaskDelay(pdMS_TO_TICKS(15 * 60 * 1000)); } } // ############### Filehandling task ############### void init_filesystem() { esp_vfs_spiffs_conf_t conf = { .base_path = "/spiffs", .partition_label = "storage", .max_files = 5, .format_if_mount_failed = true }; esp_err_t ret = esp_vfs_spiffs_register(&conf); if (ret != ESP_OK) { #if PRINT_DEBUG if (ret == ESP_FAIL) { printf("Failed to mount or format filesystem\n"); } else if (ret == ESP_ERR_NOT_FOUND) { printf("Failed to find SPIFFS partition\n"); } else { printf("Failed to initialize SPIFFS (%s)\n", esp_err_to_name(ret)); } #endif return; } #if PRINT_DEBUG printf("SPIFFS initialized successfully\n"); #endif } void file_handler_task(void *pvParameters) { while (1) { // Wait for notification from measurement task ulTaskNotifyTake(pdTRUE, portMAX_DELAY); SensorData data; FILE *f = NULL; xSemaphoreTake(xMutex, portMAX_DELAY); f = fopen("/spiffs/measurements.csv", "a"); if (f == NULL) { #if PRINT_DEBUG printf("Failed to open CSV file!\n"); #endif xSemaphoreGive(xMutex); continue; } // Write header if file is empty fseek(f, 0, SEEK_END); if (ftell(f) == 0) { fprintf(f, "Timestamp,Humidity,Temperature\n"); } // Read all data from the queue and write to CSV while (xQueueReceive(sensor_data_queue, &data, 0) == pdPASS) { fprintf(f, "%s,%.1f,%.1f\n", data.timestamp, data.humidity, data.temperature); #if PRINT_DEBUG printf("%s,%.1f,%.1f\n", data.timestamp, data.humidity, data.temperature); #endif } fclose(f); measurement_count = 0; #if PRINT_DEBUG printf("CSV file updated!\n"); #endif xSemaphoreGive(xMutex); } } // ############### Button task ############### void button_task(void *pvParameters) { // Configure BOOT button (GPIO9, active low with internal pullup) gpio_set_direction(BUTTON_PIN, GPIO_MODE_INPUT); gpio_set_pull_mode(BUTTON_PIN, GPIO_PULLUP_ONLY); uint32_t last_press_tick = 0; bool waiting_for_double = false; while (1) { if (gpio_get_level(BUTTON_PIN) == 0) { // Button pressed (active low) vTaskDelay(pdMS_TO_TICKS(50)); // Debounce if (gpio_get_level(BUTTON_PIN) == 0) { uint32_t press_start = xTaskGetTickCount(); // Wait for release while (gpio_get_level(BUTTON_PIN) == 0) { vTaskDelay(pdMS_TO_TICKS(10)); } vTaskDelay(pdMS_TO_TICKS(50)); // Debounce release uint32_t press_duration = xTaskGetTickCount() - press_start; // Only count short presses (< 500ms) if (press_duration < pdMS_TO_TICKS(500)) { if (waiting_for_double) { // ===== DOUBLE PRESS: Reset CSV ===== xSemaphoreTake(xMutex, portMAX_DELAY); FILE *f = fopen("/spiffs/measurements.csv", "w"); if (f) { fprintf(f, "Timestamp,Humidity,Temperature\n"); fclose(f); } xSemaphoreGive(xMutex); waiting_for_double = false; } else { // ===== FIRST PRESS: Wait for possible double press ===== waiting_for_double = true; last_press_tick = press_start; } } } } else { // Check if double-press window expired if (waiting_for_double && (xTaskGetTickCount() - last_press_tick) > pdMS_TO_TICKS(500)) { xTaskNotifyGive(file_handler_task_handle); // ===== SINGLE PRESS: Print CSV contents (via USB CDC) ===== xSemaphoreTake(xMutex, portMAX_DELAY); FILE *f = fopen("/spiffs/measurements.csv", "r"); if (f) { char line[256]; while (fgets(line, sizeof(line), f)) { printf("%s", line); // Goes to USB CDC (/dev/ttyACM0) } fclose(f); } xSemaphoreGive(xMutex); waiting_for_double = false; } } vTaskDelay(pdMS_TO_TICKS(10)); } } // ############### Shared buffer ############### void init_shared_buffer() { sensor_data_queue = xQueueCreate(DATA_BUFFER_SIZE, sizeof(SensorData)); xMutex = xSemaphoreCreateMutex(); if (sensor_data_queue == NULL || xMutex == NULL) { #if PRINT_DEBUG printf("Failed to create queue or mutex!\n"); #endif } } // ############### Main thread ############### void app_main(void) { // Setup setlocale(LC_ALL, "C"); // Use ASCII for text set_system_time_from_compiler(); init_shared_buffer(); init_filesystem(); // Intro #if PRINT_DEBUG printf("DHT11 temperature and humidity sensor\n"); char start_time[64] = {0}; get_current_time(start_time, sizeof(start_time)); printf("Program started at: %s\n", start_time); print_chip_info(); printf("\n\n=============\n\n"); #endif // Run tasks xTaskCreate(measure_task, "MeasureTask", 4096, NULL, 3, NULL); xTaskCreate(file_handler_task, "FileHandlerTask", 4096, NULL, 4, &file_handler_task_handle); xTaskCreate(button_task, "ButtonTask", 2048, NULL, 2, NULL); }