/* --------------------------------------------------------------
Application: 03 - Rev1
Release Type: Baseline Multitask Skeleton Starter Code
Class: Real Time Systems - Su 2025
AI Use: AI was used when I got complining errors. Also used to help comment new lines added.
---------------------------------------------------------------*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "math.h"
#include "esp_log.h" //AI Use; forgot to include it. For ESP_LOGx logging macros.
#include <string.h> //AI Use; forgot to include.// For memcpy (used in logger_task)
#include "freertos/semphr.h" //For semaphore support
//Pins
#define LED_PIN GPIO_NUM_2 // Using GPIO2 for the LED
#define LDR_PIN GPIO_NUM_32 // Using GPIO32 for Analog Out
#define LDR_ADC_CHANNEL ADC1_CHANNEL_4
#define BUTTON_GPIO GPIO_NUM_4 //Using GPIO4 for Button Press
#define AVG_WINDOW 100 // Size of averaging buffer
//Shared buffer and index
static uint16_t sensor_log[AVG_WINDOW]; // Circular buffer for raw sensor values
static int log_index = 0; // Index for sensor_log buffer
//Synchronizaation
static SemaphoreHandle_t xLogMutex; //Hand off access to the buffer
static SemaphoreHandle_t xButtonSem; //Hand off button press
//Logging Configurations
const static char *TAG = "Satellite Alarm";
#define CONFIG_LOG_DEFAULT_LEVEL_VERBOSE (1)
#define CONFIG_LOG_DEFAULT_LEVEL (5)
#define CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE (1)
#define CONFIG_LOG_MAXIMUM_LEVEL (5)
#define CONFIG_LOG_COLORS (1)
#define CONFIG_LOG_TIMESTAMP_SOURCE_RTOS (1)
//Thersholds
int UPPER_SENSOR_THRESHOLD = 26000;
int LOWER_SENSOR_THRESHOLD = 12000;
//BUffer for processed lux values
static uint16_t luxreadings[AVG_WINDOW] = {0};
static int idx = 0;
//ISR for button pressed
void IRAM_ATTR button_isr_handler(void *arg){
ESP_LOGV(TAG, "button pressed - setting semaphore to be taken by logger \n");
BaseType_t xHigherPrioTaskWoken = pdFALSE; // AI Use; False was all lower case
xSemaphoreGiveFromISR(xButtonSem, &xHigherPrioTaskWoken);
portYIELD_FROM_ISR(xHigherPrioTaskWoken);
}
// Task: Read sensor and store in buffer
void sensor_task(void *arg) {
ESP_LOGV(TAG,"sensor entered \n");
while (1) {
// Simulate reading light sensor value
uint16_t val = xTaskGetTickCount() % 4096;
// Lock mutex to write value to buffer || What happens if you lock it for too short or long of a time? It can starve other tasks
if (xSemaphoreTake(xLogMutex, pdMS_TO_TICKS(10))) {
ESP_LOGV(TAG,"sensor taking log data semaphore for write\n");
sensor_log[log_index] = val;
// this circular buffer will have issues at startup and when cleared; no clearing logic here
log_index = (log_index + 1) % AVG_WINDOW;
xSemaphoreGive(xLogMutex); //give up the semaphore!
ESP_LOGV(TAG,"sensor releasing log data semaphore\n");
}
vTaskDelay(pdMS_TO_TICKS(200)); //Runs every 200ms
}
}
// Task: Wait for button, "compress" and dump log
void logger_task(void *arg) {
TickType_t currentTime = pdTICKS_TO_MS(xTaskGetTickCount()); //Used for debounce
TickType_t previousTime = 0; //Used for debounce
while (1) {
// Block until ISR signals button press
if (xSemaphoreTake(xButtonSem, portMAX_DELAY) == pdTRUE) {
previousTime = currentTime;
currentTime = pdTICKS_TO_MS(xTaskGetTickCount());
if ((currentTime - previousTime) > 300){ //For debouncing
ESP_LOGV(TAG,"Button pressed and entered Logger. Compressing log; will release on exit\n");
uint16_t copy[AVG_WINDOW]; //Temporarly copy buffer
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, luxreadings, sizeof(copy)); //Copy current lux buffer
count = idx;
xSemaphoreGive(xLogMutex);
ESP_LOGV(TAG,"logger releasing log data semaphore\n");
}
// Simulate compression (calculate stats)
uint16_t min = 4095, max = 0;
uint32_t sum = 0;
for (int i = 0; i < AVG_WINDOW; i++) {
if (copy[i] < min) min = copy[i];
if (copy[i] > max) max = copy[i];
sum += copy[i];
}
uint16_t avg = sum / AVG_WINDOW;
ESP_LOGI(TAG, "[LOG DUMP] readings %d: min=%d, max=%d, avg=%d", AVG_WINDOW, min, max, avg);
// consider - this code doesn't really "dump" the buffer does it? No, it just sends it to the console
}
}
}
}
// Task to blink an LED at 1 Hz (1400 ms period: 700 ms ON, 700 ms OFF)
void satellite_heartbeat(void *pvParameters) {
bool satellite_led_on = false;
TickType_t currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
while (1) {
currentTime = pdTICKS_TO_MS(xTaskGetTickCount());
gpio_set_level(LED_PIN, 1);
satellite_led_on = !satellite_led_on; // toggle state for next time
vTaskDelay(pdMS_TO_TICKS(700)); // Delay for 700 ms using MS to Ticks Function vs alternative which is MS / ticks per ms
gpio_set_level(LED_PIN, 0);
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_task(void *pvParameters) {
TickType_t currentTime = pdTICKS_TO_MS(xTaskGetTickCount());
TickType_t previousTime = 0;
while (1) {
previousTime = currentTime;
currentTime = pdTICKS_TO_MS(xTaskGetTickCount());
printf("System is active. Time of data sent: %lu [period = %lu]!\n",currentTime, currentTime-previousTime);
vTaskDelay(pdMS_TO_TICKS(7000)); // Delay for 7000 ms
}
vTaskDelete(NULL); // We'll never get here; tasks run forever
}
//Task: Reads light sensor and computes average lux
void light_sensor_task(void *pvParameters){
TickType_t currentTime = pdTICKS_TO_MS(xTaskGetTickCount());
TickType_t previousTime = 0;
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC_CHANNEL_4, ADC_ATTEN_DB_11);
// Variables to compute LUX
int raw;
float Vmeasure = 0.;
float Rmeasure = 0.;
float lux = 0.;
// Variables for moving average
float sum = 0;
float GAMMA = .7;
float RL10 = 50;
for(int i = 0; i < AVG_WINDOW; ++i) {
raw = adc1_get_raw(LDR_ADC_CHANNEL);
Vmeasure = (raw/4095.0)*3.3;
Rmeasure = (Vmeasure * 10000) / (3.3 - Vmeasure);
lux = pow(RL10 * 1e3 * pow(10, GAMMA) / Rmeasure, (1 / GAMMA));
luxreadings[i] = lux;
sum += luxreadings[i];
}
const TickType_t periodTicks = pdMS_TO_TICKS(200); //200ms period
TickType_t lastWakeTime = xTaskGetTickCount(); // initialize last wake time
while(1){
//Read current sensor value
raw = adc1_get_raw(LDR_ADC_CHANNEL);
//Compute LUX
Vmeasure = (raw/4095.0)*3.3;
Rmeasure = (Vmeasure * 10000) / (3.3 - Vmeasure);
lux = pow(RL10 * 1e3 * pow(10, GAMMA) / Rmeasure, (1 / GAMMA));
//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
//Checking Threshold and print
if (avg > UPPER_SENSOR_THRESHOLD){
printf("**ALERT**: Light Sensor average %d exceeds threshold %d! Increasing above 26000 LUX will jeopardize Fail-Safe Recovery\n", avg, UPPER_SENSOR_THRESHOLD);
}
else if (avg < LOWER_SENSOR_THRESHOLD){
printf("**ALERT**: Light Sensor average %d has fallen below the threshold %d! Decreasing below 12000 LUX will jeopardize Fail-Safe Recovery\n", avg, LOWER_SENSOR_THRESHOLD);
}
else{
previousTime = currentTime;
currentTime = pdTICKS_TO_MS(xTaskGetTickCount());
printf("Light Sensor average is: %d LUX \n", avg);
//Print time period
printf("Light sensor data is good at time: %lu [period = %lu]!\n",currentTime, currentTime-previousTime);
}
if (xSemaphoreTake(xLogMutex, pdMS_TO_TICKS(10))) {
ESP_LOGV(TAG,"sensor taking log data semaphore for write\n");
luxreadings[idx] = avg;
// this circular buffer will have issues at startup and when cleared; no clearing logic here
idx = (idx + 1) % AVG_WINDOW;
xSemaphoreGive(xLogMutex); //give up the semaphore!
ESP_LOGV(TAG,"sensor releasing log data semaphore\n");
}
vTaskDelayUntil(&lastWakeTime, periodTicks);
// for (volatile int i = 0; i < 1000000; ++i) { //Bonus!
// }
}
}
void app_main() {
esp_log_level_set(TAG, ESP_LOG_INFO);
// Create synchronization primitives
xLogMutex = xSemaphoreCreateMutex();
xButtonSem = xSemaphoreCreateBinary();
assert(xLogMutex && xButtonSem);
gpio_install_isr_service(0);
ESP_LOGI(TAG,"configuring button\n");
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);
// GPIO for input mode
gpio_reset_pin(LDR_PIN);
gpio_set_direction(LDR_PIN, GPIO_MODE_INPUT);
// Set ADC to 12-bit resolution
adc1_config_width(ADC_WIDTH_BIT_12);
//Set Attenuation
adc1_config_channel_atten(LDR_ADC_CHANNEL, ADC_ATTEN_DB_11);
// Initialize LED GPIO
gpio_reset_pin(LED_PIN);
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
xTaskCreatePinnedToCore(satellite_heartbeat, "Blink Task", 2048, NULL, 1, NULL,1);
xTaskCreatePinnedToCore(print_task, "Print Task", 4096, NULL, 1, NULL,1);
xTaskCreatePinnedToCore(light_sensor_task, "Sensor Task", 4096, NULL,2,NULL, 1);
xTaskCreatePinnedToCore(logger_task, "Logger Task", 4096, NULL, 3, NULL, 1);
ESP_LOGI(TAG, "System ready. Press the button to dump the log.");
}