/* --------------------------------------------------------------
Application: 03 - Rev1
Name: Kelvin Vu
Release Type:
Class: Real Time Systems - Fall 2025
AI Use: Commented inline
---------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h" // useful for analog to digital conversion
#include "math.h"
#include "freertos/semphr.h" // Added from example
#include "esp_log.h"
const static char *TAG = "UCF-DEMO>";
#define LED_PIN GPIO_NUM_2 // Using GPIO2 for the LED
#define LDR_PIN GPIO_NUM_32
//LDR_PIN to gpio pin 32
#define LDR_ADC_CHANNEL ADC1_CHANNEL_4
// LDR_ADC_CHANNEL -- if you used gpio pin 32 it should map to ADC1_CHANNEL4
// TODO99: Consider Adding AVG_WINDOW and SENSOR_THRESHOLD as global defines
#define BUTTON_GPIO GPIO_NUM_4 // GPIO4 for BUTTON
#define LOG_BUFFER_SIZE 50 // N = 50
// Shared buffer and index
static uint16_t sensor_log[LOG_BUFFER_SIZE];
static int log_index = 0;
// Recommended approach with ISR + semaphore
static SemaphoreHandle_t xButtonSem;
static SemaphoreHandle_t xLogMutex;
// === ISR: Triggered on button press ===
void IRAM_ATTR button_isr_handler(void *arg) {
// Button debouncer timing
TickType_t currentTime = xTaskGetTickCountFromISR();
static TickType_t previousTime = 0;
TickType_t elapsed = currentTime - previousTime;
if (elapsed > pdMS_TO_TICKS(200)){ // Button debouncer
ESP_LOGV(TAG,"button pressed - setting semaphore to be taken by logger \n");
BaseType_t xHigherPrioTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xButtonSem, &xHigherPrioTaskWoken);
portYIELD_FROM_ISR(xHigherPrioTaskWoken);
previousTime = currentTime;
}
}
void status_led_task(void *pvParameters) {
bool led_status = false;
//TickType_t currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
while (1) {
//currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
gpio_set_level(LED_PIN, led_status); // Set LED pin high or low based on led_status flag;
led_status = !led_status; // toggle state for next loop
//printf("LED Status %s @ %lu\n", led_status ? "ON" : "OFF", currentTime);
vTaskDelay(pdMS_TO_TICKS(700)); // Delay for 700 ms using MS to Ticks Function vs alternative which is MS / ticks per ms
}
vTaskDelete(NULL); // We'll never get here; tasks run forever
}
// Task to print a message every 7000 ms (7 seconds)
void print_status_task(void *pvParameters) {
TickType_t currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
TickType_t previousTime = 0;
while (1) {
previousTime = currentTime;
currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
// Prints a periodic message based on a thematic area. Output a timestamp (ms) and period (ms)
printf("Signal received @: %lu ms [Signal period = %lu ms]!\n", currentTime, currentTime-previousTime);
vTaskDelay(pdMS_TO_TICKS(7000)); // Delay for 7000 ms
}
vTaskDelete(NULL); // We'll never get here; tasks run forever
}
//Create new task for sensor reading every 1000ms
void sensor_task(void *pvParameters) {
//TODO110 Configure ADC (12-bit width, 0-3.3V range with 11dB attenuation)
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_12); //could be ADC_ATTEN_DB_11
//TODO11a consider where AVG_WINDOW is defined, it could be here, or global value
int AVG_WINDOW = 10;
int SENSOR_THRESHOLD = 10000; // Space, almost direct sunlight
// Variables to compute LUX
int raw;
float Vmeasure = 0.;
float Rmeasure = 0.;
float lux = 0.;
// Variables for moving average
int luxreadings[AVG_WINDOW] = {};
int idx = 0;
float sum = 0;
//See TODO 99
// Pre-fill the readings array with an initial sample to avoid startup anomaly
for(int i = 0; i < AVG_WINDOW; ++i) {
raw = adc1_get_raw(LDR_ADC_CHANNEL);
Vmeasure = raw / 4096. * 3.3; //TODO11b correct this with the equation seen earlier
Rmeasure = 10000 * Vmeasure / (1 - Vmeasure / 3.3); //TODO11c correct this with the equation seen earlier
//printf("Analog Value Read is %d!\n", raw);
//printf("Voltage measured is %f!\n", Vmeasure);
//printf("Rmeasured is %f!\n", Rmeasure);
lux = pow(50 * 1000 * pow(10, 0.7) / Rmeasure, (1 / 0.7)); //TODO11d correct this with the equation seen earlier
luxreadings[i] = lux;
sum += luxreadings[i];
}
const TickType_t periodTicks = pdMS_TO_TICKS(1000); // e.g. 1000 ms period
//const TickType_t periodTicks = pdMS_TO_TICKS(800); // e.g. 500 + 300 ms period
TickType_t lastWakeTime = xTaskGetTickCount(); // initialize last wake time
while (1) {
// Read current sensor value
raw = adc1_get_raw(LDR_ADC_CHANNEL);
//printf("**raw **: Sensor %d\n", raw); // raw value
// Compute LUX
Vmeasure = raw / 4096. * 3.3; //TODO11e correct this with the equation seen earlier
Rmeasure = 3030 * Vmeasure / (1 - Vmeasure / 3.3); //TODO11f correct this with the equation seen earlier
lux = pow(50 * 1000 * pow(10, 0.7) / Rmeasure, (1 / 0.7)); //TODO11g correct this with the equation seen earlier
// Using 3.3 V makes lux not equal 1=1
// Update moving average buffer
sum -= luxreadings[idx]; // remove oldest value from sum
luxreadings[idx] = lux; // place new reading
sum += lux; // add new value to sum
idx = (idx + 1) % AVG_WINDOW;
int avg = sum / AVG_WINDOW; // compute average
//int avg = sum / AVG_WINDOW // Original code
//TODO11h Check threshold and print alert if exceeded or below based on context
if (avg < SENSOR_THRESHOLD) {
printf("**Alert**: Solar Energy Average: [%d] below threshold [%d]!\n", avg, SENSOR_THRESHOLD);
} else {
//
printf("Solar Energy Average is %d!\n", avg);
// (you could print the avg value for debugging)
}
//TODO11j: Print out time period [to help with answering Eng/Analysis quetionst (hint check Application Solution #1 )
//https://wokwi.com/projects/430683087703949313
if (xSemaphoreTake(xLogMutex, pdMS_TO_TICKS(10))) {
ESP_LOGV(TAG,"sensor taking log data semaphore for write\n");
sensor_log[log_index] = lux;
// this circular buffer will have issues at startup and when cleared; no clearing logic here
log_index = (log_index + 1) % LOG_BUFFER_SIZE;
xSemaphoreGive(xLogMutex); //give up the semaphore!
ESP_LOGV(TAG,"sensor releasing log data semaphore\n");
}
TickType_t currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
TickType_t previousTime = 0;
previousTime = currentTime;
currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
// Prints a periodic message based on a thematic area. Output a timestamp (ms) and period (ms)
//printf("Sensor Period is %lu ms\n", currentTime);
vTaskDelayUntil(&lastWakeTime, periodTicks);
//vTaskDelay(pdMS_TO_TICKS(200));
//TODO11k Replace vTaskDelay with vTaskDelayUntil with parameters &lastWakeTime and periodTicks
//vTaskDelayUntil(&lastWakeTime, periodTicks);
}
}
// Task: Wait for button, "compress" and dump log
void logger_task(void *arg) {
while (1) {
// Block until ISR signals button press
if (xSemaphoreTake(xButtonSem, portMAX_DELAY) == pdTRUE) {
ESP_LOGV(TAG,"logger entered based on button semaphore; will release on exit\n");
TickType_t buttonTime = pdTICKS_TO_MS( xTaskGetTickCount() );
static TickType_t previousTime = 0;
printf("Compressing sensor logs. Time elasped since last press [%lu] \n", buttonTime-previousTime);
previousTime = buttonTime;
uint16_t copy[LOG_BUFFER_SIZE];
int count = 0;
// Lock mutex to read from buffer
if (xSemaphoreTake(xLogMutex, pdMS_TO_TICKS(100))) {
ESP_LOGV(TAG,"logger took log data semaphore for copy\n");
memcpy(copy, sensor_log, sizeof(copy));
count = log_index;
xSemaphoreGive(xLogMutex);
ESP_LOGV(TAG,"logger releasing log data semaphore\n");
}
// Simulate compression (calculate stats)
uint16_t min = 0, max = 0;
uint32_t sum = 0;
for (int i = 0; i < LOG_BUFFER_SIZE; i++) {
if (copy[i] < min) min = copy[i];
if (copy[i] > max) max = copy[i];
sum += copy[i];
}
uint16_t avg = sum / LOG_BUFFER_SIZE;
ESP_LOGI(TAG, "[LOG DUMP] readings %d: min=%d, max=%d, avg=%d", LOG_BUFFER_SIZE, min, max, avg);
printf("N = %d: min = %d, max = %d, avg = %d \n", LOG_BUFFER_SIZE, min, max, avg);
// consider - this code doesn't really "dump" the buffer does it?
//copy[LOG_BUFFER_SIZE] = 0;
//min = 4095, max = 0;
}
}
}
void app_main() {
esp_log_level_set(TAG, ESP_LOG_INFO);
xButtonSem = xSemaphoreCreateBinary();
xLogMutex = xSemaphoreCreateMutex();
assert(xLogMutex && xButtonSem);
gpio_install_isr_service(0);
// Initialize LED GPIO
gpio_reset_pin(LED_PIN);
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
gpio_reset_pin(LDR_PIN);
gpio_set_direction(LDR_PIN, GPIO_MODE_INPUT);
gpio_reset_pin(BUTTON_GPIO);
gpio_set_direction(BUTTON_GPIO, GPIO_MODE_INPUT);
gpio_pullup_en(BUTTON_GPIO);
gpio_set_intr_type(BUTTON_GPIO, GPIO_INTR_NEGEDGE);
gpio_isr_handler_add(BUTTON_GPIO, button_isr_handler, NULL);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(LDR_ADC_CHANNEL, ADC_ATTEN_DB_11);
xTaskCreatePinnedToCore(status_led_task, "Status BEACON", 2048, NULL, 1, NULL, 1); // Low priority
xTaskCreatePinnedToCore(print_status_task, "System STATUS", 4096, NULL, 1, NULL, 1); // Low priority
xTaskCreatePinnedToCore(sensor_task, "System SENSOR", 4096, NULL, 2, NULL, 1); // Med priority
xTaskCreatePinnedToCore(logger_task, "System LOGGER", 4096, NULL, 3, NULL, 1); // High priority
// Non-pinned task
//xTaskCreate(status_led_task, "Status BEACON", 2048, NULL, 1, NULL); // Low priority
//xTaskCreate(print_status_task, "System STATUS", 4096, NULL, 1, NULL); // Low priority
//xTaskCreate(sensor_task, "System SENSOR", 4096, NULL, 2, NULL); // Med priority
//xTaskCreate(logger_task, "System LOGGER", 4096, NULL, 3, NULL); // High priority
}