/* --------------------------------------------------------------
Application: 04 - Rev1
Release Type: Use of Memory Based Task Communication
Class: Real Time Systems - Su 2025
Author: [R Rosario]
Email: [[email protected]]
Company: [University of Central Florida]
AI Use: Please commented inline where you use(d) AI
---------------------------------------------------------------*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_log.h"
//TODO 8 - Update the code variables and comments to match your selected thematic area!
//TODO 0a connect the components in the diagram to the GPIO pins listed below.
//Note even if Wokwi won't blow your LEDs, we'll assume them to be off if they're not connected to resistors!
//Next, goto TODO 0b
// SPACE THEME COMPONENTS
#define LED_GREEN GPIO_NUM_5 // System status LED
#define LED_RED GPIO_NUM_4 // Radiation alert LED
#define BUTTON_PIN GPIO_NUM_18 // Ground control button
#define POT_ADC_CHANNEL ADC1_CHANNEL_6 // Radiation sensor (GPIO34)
// AI-GENERATED: Calculated as (1000ms/100ms delay) * 30s = 300 events
#define MAX_COUNT_SEM 300 // Adjusted for 30s worst-case (300 events)
// TODO 7: Based ont the speed of events;
//can you adjust this MAX Semaphore Counter to not miss a high frequency threshold events
//over a 30 second time period, e.g., assuming that the sensor exceeds the threshold of 30 seconds,
//can you capture every event in your counting semaphore? what size do you need?
// Threshold for analog sensor
//TODO 1: Adjust threshold based on your scenario or input testing
// You should modify SENSOR_THRESHOLD to better match your Wokwi input behavior;
// note the min/max of the adc raw reading
#define SENSOR_THRESHOLD 3000 // Radiation danger threshold
// Handles for semaphores and mutex - you'll initialize these in the main program
SemaphoreHandle_t sem_button;
SemaphoreHandle_t sem_sensor;
SemaphoreHandle_t print_mutex;
volatile int SEMCNT = 0; //You may not use this value in your logic -- but you can print it if you wish
// AI-GENERATED: Added for rising edge detection
bool last_sensor_state = false; // For rising edge detection
//TODO 0b: Set heartbeat to cycle once per second (on for one second, off for one second)
//Find TODO 0c
// Space-themed heartbeat (1Hz blink)
void heartbeat_task(void *pvParameters) {
while (1) {
gpio_set_level(LED_GREEN, 1);
vTaskDelay(pdMS_TO_TICKS(1000)); // 1.0s on
gpio_set_level(LED_GREEN, 0);
vTaskDelay(pdMS_TO_TICKS(1000)); // 1.0s off
}
}
void sensor_task(void *pvParameters) {
while (1) {
int rad_level = adc1_get_raw(POT_ADC_CHANNEL);
// AI-GENERATED: Rising edge detection logic
bool current_state = (rad_level > SENSOR_THRESHOLD);
//TODO 2: Add serial print to log the raw sensor value (mutex protected)
//Hint: use xSemaphoreTake( ... which semaphore ...) and printf
// Protected radiation level logging
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("Radiation level: %d uSv/h\n", rad_level);
xSemaphoreGive(print_mutex);
// Rising edge detection for threshold crossing
// AI-GENERATED: Edge detection prevents spamming
if (current_state && !last_sensor_state) {
if(SEMCNT < MAX_COUNT_SEM) SEMCNT++; // DO NOT REMOVE THIS LINE
//TODO 3: prevent spamming by only signaling on rising edge; See prior application #3 for help!
xSemaphoreGive(sem_sensor); // Signal radiation alert
}
last_sensor_state = current_state;
vTaskDelay(pdMS_TO_TICKS(100)); // 10Hz monitoring
}
}
void button_task(void *pvParameters) {
// AI-GENERATED: Debounce variables
uint32_t last_press_time = 0;
const TickType_t debounce_delay = pdMS_TO_TICKS(50);
while (1) {
if (gpio_get_level(BUTTON_PIN) == 0) { // Button pressed (active low)
TickType_t now = xTaskGetTickCount();
// TODO 4a: Add addtional logic to prevent bounce effect (ignore multiple events for 'single press')
// You must do it in code - not by modifying the wokwi simulator button
// AI-GENERATED: Hardware debounce implementation
if ((now - last_press_time) > debounce_delay) {
xSemaphoreGive(sem_button);
//TODO 4b: Add a console print indicating button was pressed (mutex protected); different message than in event handler
// Protected button press logging
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("Ground control button pressed\n");
xSemaphoreGive(print_mutex);
last_press_time = now;
}
}
vTaskDelay(pdMS_TO_TICKS(10)); // Do Not Modify This Delay!
}
}
void event_handler_task(void *pvParameters) {
while (1) {
// Handle radiation alerts
if (xSemaphoreTake(sem_sensor, 0)) {
SEMCNT--; //DO NOT MODIFY THIS LINE
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("ALERT! Radiation threshold exceeded!\n");
xSemaphoreGive(print_mutex);
for(int i=0; i<3; i++) {
gpio_set_level(LED_RED, 1);
vTaskDelay(pdMS_TO_TICKS(200));
gpio_set_level(LED_RED, 0);
vTaskDelay(pdMS_TO_TICKS(200));
}
}
// Handle ground control commands
if (xSemaphoreTake(sem_button, 0)) {
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("Ground command: Safety protocols engaged!\n");
xSemaphoreGive(print_mutex);
// Visual confirmation
gpio_set_level(LED_RED, 1);
vTaskDelay(pdMS_TO_TICKS(1000));
gpio_set_level(LED_RED, 0);
}
vTaskDelay(pdMS_TO_TICKS(10)); // Yield CPU
}
}
void app_main(void) {
// Configure output LEDs
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << LED_GREEN) | (1ULL << LED_RED),
.mode = GPIO_MODE_OUTPUT,
};
gpio_config(&io_conf);
// Configure ground control button
gpio_config_t btn_conf = {
.pin_bit_mask = (1ULL << BUTTON_PIN),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE
};
gpio_config(&btn_conf);
// Configure radiation sensor (ADC)
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(POT_ADC_CHANNEL, ADC_ATTEN_DB_11);
// Create sync primitives
// TODO 0c: Attach the three SemaphoreHandle_t defined earlier
// (sem_button, sem_sensor, print_mutex) to appropriate Semaphores.
// binary, counting, mutex by using the appropriate xSemaphoreCreate APIs.
// the counting semaphore should be set to (MAX_COUNT_SEM,0);
// Move on to TODO 1; remaining TODOs are numbered 1,2,3, 4a 4b, 5, 6 ,7
sem_button = xSemaphoreCreateBinary();
sem_sensor = xSemaphoreCreateCounting(MAX_COUNT_SEM, 0);
print_mutex = xSemaphoreCreateMutex();
//TODO 5: Test removing the print_mutex around console output (expect interleaving)
//Observe console when two events are triggered close together
// Create tasks with space-themed priorities
xTaskCreate(heartbeat_task, "System_Heartbeat", 2048, NULL, 1, NULL);
xTaskCreate(sensor_task, "Radiation_Monitor", 2048, NULL, 3, NULL);
xTaskCreate(button_task, "Ground_Control", 2048, NULL, 4, NULL);
xTaskCreate(event_handler_task, "Mission_Control", 2048, NULL, 5, NULL);
// Disaster mode enabled!
// xTaskCreate(heartbeat_task, "HEART", 2048, NULL, 1, NULL); // Min priority
// xTaskCreate(sensor_task, "RAD", 2048, NULL, 5, NULL); // Max priority
// xTaskCreate(button_task, "Ground_Control", 2048, NULL, 1, NULL);
// xTaskCreate(event_handler_task, "ALERT", 2048, NULL, 5, NULL);
//TODO 6: Experiment with changing task priorities to induce or fix starvation
//E.G> Try: xTaskCreate(sensor_task, ..., 4, ...) and observe heartbeat blinking
//You should do more than just this example ...
}