/* --------------------------------------------------------------
Application: 01 - Rev1: Martian Greenhouse
Release Type: Baseline Multitask Skeleton Starter Code
Class: Real Time Systems - Su 2025
Theme: Earth Spacehub Communication Link Blink and Mars Greenhouse Telemetry System
AI Use: Commented inline.
---------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
// TODO1:
#include "driver/adc.h"
#include "math.h"
#define LED_PIN GPIO_NUM_4 // Using GPIO4 for the LED
#define LDR_PIN GPIO_NUM_32 // TODO2: AO on 32
#define LDR_ADC_CHANNEL ADC1_CHANNEL_4 // TODO3: ADC Channel connected to PIN32
#define AVG_WINDOW 10
// TODO99: Consider Adding AVG_WINDOW and SENSOR_THRESHOLD as global defines
// simulate environment variable data for theme
// note: AI used to generate a realistic random range for values
float get_oxygen_level() {
return 20.0 + (rand() % 10) / 10.0; // ~20.0–21.0%
}
// TODO9:
// LED blink showing Earth Spacehub communication link is active ( 500 ms ON, 500 ms OFF)
void commLink_task(void *pvParameters) {
bool led_on = false;
TickType_t last_wake = xTaskGetTickCount();
while (1) {
// variables to keep track of period and timestaps
TickType_t now = xTaskGetTickCount();
unsigned long time_ms = now * portTICK_PERIOD_MS;
unsigned long period_ms = (now - last_wake) * portTICK_PERIOD_MS;
last_wake = now;
// TODO: Set LED pin high or low based on led_on flag; right now it's always on... boring; hint in the commented out print statement
if (led_on){
unsigned long timestamp = (unsigned long)(xTaskGetTickCount() * portTICK_PERIOD_MS);
gpio_set_level(LED_PIN, 1); // LED ON
printf("\n***Earth Spacehub CommLink: [ACTIVE]*** \nBeacon ON at %lu ms", timestamp);
} else {
unsigned long timestamp = (unsigned long)(xTaskGetTickCount() * portTICK_PERIOD_MS);
gpio_set_level(LED_PIN, 0); // LED OFF
printf("\nBeacon OFF at %lu ms", timestamp);
}
printf(" Task Period: %lu ms\n", period_ms); // Log task cycle time
led_on = !led_on; // toggle state
vTaskDelay(pdMS_TO_TICKS(500)); // Delay for 500 ms using MS to Ticks Function vs alternative which is MS / ticks per ms
}
vTaskDelete(NULL); // We'll never get here; tasks run forever
}
// TODO10: Log Martian Greenhouse Environment Status to console every 1s 1000ms
void print_task(void *pvParameters) {
TickType_t last_telemetry = xTaskGetTickCount();
while (1) {
TickType_t now = xTaskGetTickCount();
unsigned long time_ms = now * portTICK_PERIOD_MS;
unsigned long period_ms = (now - last_telemetry) * portTICK_PERIOD_MS;
last_telemetry = now;
// generate values for greenhouse simulation
float oxygen = get_oxygen_level();
// AI used to create a readable console statement
printf("Martian greenhouse up and running @ Time: %lu ms Period: %lums with O₂: %.1f%% ",
time_ms, period_ms, oxygen); vTaskDelay(pdMS_TO_TICKS(1000)); // 1 second delay. needed to prevent starvation
}
vTaskDelete(NULL); // We'll never get here; tasks run forever
}
// TODO11: sesnor readings every 500ms
void sunlightSensor_task(void *pvParameters) {
// TODO10: configure ADC channel
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(LDR_ADC_CHANNEL, ADC_ATTEN_DB_11);
// TODO11a Variables to compute LUX. AVE_WINDOW globally defined
int raw, SENSOR_THRESHOLD = 500;
float Vmeasure = 0., Rmeasure = 0., lux = 0.;
// Variables for moving average
int luxreadings[AVG_WINDOW] = {0}, idx = 0;
float sum = 0;
// 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 / 4095.0) * 3.3; //TODO11b correct this with the equation seen earlier
Rmeasure = 10000.0 / ((3.3 - Vmeasure) -1); //TODO11c correct this with the equation seen earlier
lux = pow((50.0 * 1000.0) / Rmeasure, 1.0 / 0.7); //TODO11d correct this with the equation seen earlier
luxreadings[i] = lux;
sum += luxreadings[i];
}
const TickType_t periodTicks = pdMS_TO_TICKS(500); // e.g. 500 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);
// Compute LUX
Vmeasure = (raw / 4095.0) * 3.3; //TODO11e correct this with the equation seen earlier
Rmeasure = (Vmeasure * 10000.0) / (3.3 - Vmeasure); //TODO11f correct this with the equation seen earlier
lux = pow((50.0 * 1000.0) / Rmeasure, 1.0 / 0.7); //TODO11g correct this with the equation seen earlier
// 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
//TODO11h Check threshold and print alert if exceeded or below based on context
if (avg > SENSOR_THRESHOLD) {
printf("**Alert**: Sunlight exposure too high! Plants can burn! Avg %d > threshold %d\n", avg, SENSOR_THRESHOLD);
} else {
//TODO11i
printf("Sunlight OK: Avg lux is %d\n", avg); // for debugging
}
//TODO11j: Print out time period [to help with answering Eng/Analysis quetionst (hint check Application Solution #1 )
unsigned long timestamp = xTaskGetTickCount() * portTICK_PERIOD_MS;
printf("Sunlight Sensor Task Time Period: %lu ms\n", timestamp);
//TODO11k Replace vTaskDelay with vTaskDelayUntil with parameters &lastWakeTime and periodTicks
//vTaskDelay(periodTicks); OLD!!
vTaskDelayUntil(&lastWakeTime, periodTicks); // NEW
//**** IF vTASKDELAYUNTIL IS REMOVED, THE SYSTEM WILL BE STARVED!
}
}
void app_main() {
// Initialize LED GPIO
gpio_reset_pin(LED_PIN);
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
// TODO4: initialize LDR and configure ADC
gpio_reset_pin(LDR_PIN);
gpio_set_direction(LDR_PIN, GPIO_MODE_INPUT);
// create tasks on core1, saving core0 for wifi etc...
// note: the higher the number the higher the priority
// (ptr_name, "desc name", stack depth, opt para, priority 0 low, opt ptr ref, core #)
xTaskCreatePinnedToCore(commLink_task, "Earth CommLink Beacon Task", 2048, NULL, 0, NULL, 1); // TODO 12
xTaskCreatePinnedToCore(print_task, "Greenhouse Environment Telemetry Task", 2048, NULL, 1, NULL, 1); // TODO 8
xTaskCreatePinnedToCore(sunlightSensor_task, "SENSOR", 2048, NULL, 2, NULL, 1); // TODO 8
// the sunlightSensor task has the highest priority, then the print and then the LED blink.
}