/*
Real Time Systems Spring 2026
Roller Coaster Design System
Hardware Sketch
ESP32
2 external LEDs
-Green - Heartbeat shows ride is operating
-Red - Emergency Stop
1 sensor
-Light Sensor - Measures proximity of rollercoaster in a zone
1 momentary input
-Button - Emergency stop button
Tasks & ISRs
4 FreeRTOS tasks
-xTaskCreatePinnedToCore(e_stop_logger_task, "ESTOP Pressed", 4096, NULL, 3, NULL, 1);
- This task is a hard requirement. The system must log when an estop was pressed and the conditions
around when it was pressed. Failure would indicate the system does not know when an estop was pressed
and that the ride could potentially still be running. Unknown estop is a failure.
- This task also prints when the train is detected to be in a critical zone. This is a hard requirement
it is critical that the system knows when a train is in this zone.
-xTaskCreatePinnedToCore(ride_running_task, "Ride ON", 2048, NULL, 1, NULL,1);
- This task indicates that the system is operating under normal conditions with a green LED blink
-xTaskCreatePinnedToCore(print_status_task, "STATUS", 2048, NULL, 1, NULL,1);
- This task indicates the update status of the system. It is a soft requirement. It indicates how long
it has been since the last update.
-xTaskCreatePinnedToCore(proximity_sensor_task, "Proximity Sensor", 4096, NULL,2,NULL,1);
- This task reads measurements from the light sensor. If the sensor is covered and not sensing light a
train is present. This is a hard requirement.
-xTaskCreatePinnedToCore(zone_monitor_task, "Zone monitoring", 4096, NULL,2,NULL,1);
- This task consumes data from the queue at the same pace as its recording, it then outputs when
the train is in the critical zone. This is a hard requirement.
1 ISR
-IRAM_ATTR button_isr_handler()
- This is the estop button. A press should alert the system that something has happened to the ride system
and needs to be investigated. This is a hard requirement
at least 1 task must take variable amount of time
Timing Declare a period/deadline for every task
mark Hard vs Soft in code comments Hard miss = failure for your product scenario.
Synchronization Use
2 distinct mechanisms (mutex, queue, binary sem, critical section…) Show why each is needed.
- xLogMutex - this is used to log sensor readings into the buffer
- xButtonSem - this is used as a flag to show the event handler that a button has been
pressed
- xSensorQueue - this is used to hold sensor readings from the proximity_sensor_task (producer)
to the zone_monitor_task (consumer)
Inter‑task & External Comms One internal channel (queue, event group, etc.)
- xSensorQueue holds the readings from the light sensor
one outward link (UART, Wi‑Fi, I²C, …) External link can talk to Wokwi serial monitor or a simple web page.
- The external link are the loggers in the wokwi serial monitor
Use Wokwi logic‑analyzeLinks to an external site.r [full credit] or serial time‑stamps [partial credit]
AI used for brainstorming possible ride system tasks
https://claude.ai/chat/8b69ee2a-55fc-4fe2-a231-17321fa86bed
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "math.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_continuous.h"
#include "esp_log.h"
#include <string.h>
#include "esp_timer.h"
// basic board configurations
#define RED_LED_PIN GPIO_NUM_2 // Using GPIO2 for the red LED
#define GREEN_LED_PIN GPIO_NUM_27 // Using GPIO for the green LED
#define TRIG_PIN GPIO_NUM_5 // ultrasonic trigger
#define ECHO_PIN GPIO_NUM_18 // ultrasonic echo
#define SENSOR_THRESHOLD_CM 180
#define BUTTON_GPIO GPIO_NUM_4 // push button
#define LOG_BUFFER_SIZE 100
const static char *TAG = "APP-3>";
#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)
static bool estopped = false;
// Synchronization
static SemaphoreHandle_t xButtonSem; // Hand off on button press!
static QueueHandle_t xSensorQueue; // holds ADC readings
// ISR: Triggered on button press
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;
xSemaphoreGiveFromISR(xButtonSem, &xHigherPrioTaskWoken);
portYIELD_FROM_ISR(xHigherPrioTaskWoken);
}
adc_oneshot_unit_handle_t adc1_handle;
//LED toggles every 1.4 seconds modified from application 2
void ride_running_task(void *pvParameters) {
bool led_status = false;
TickType_t currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
while (1) {
currentTime = pdTICKS_TO_MS( xTaskGetTickCount() );
led_status = !led_status;
gpio_set_level(GREEN_LED_PIN, led_status);
ESP_LOGV(TAG,"LED Cycle %s @ %lu\n", led_status ? "ON" : "OFF", currentTime);
vTaskDelay(pdMS_TO_TICKS(1400)); // Delay for 1400 ms or 1.4 seconds
}
vTaskDelete(NULL);
}
// Task to print a message every 5000 ms (5 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)
ESP_LOGI(TAG,"Ride control system operational @ time %lu [period = %lu]!\n",currentTime, currentTime-previousTime);
vTaskDelay(pdMS_TO_TICKS(5000)); // Delay for 5000 ms
}
vTaskDelete(NULL);
}
static int prox_read_cm(void) {
// Send 10 µs trigger pulse
gpio_set_level(TRIG_PIN, 0);
esp_rom_delay_us(2);
gpio_set_level(TRIG_PIN, 1);
esp_rom_delay_us(10);
gpio_set_level(TRIG_PIN, 0);
int64_t start = esp_timer_get_time();
while (gpio_get_level(ECHO_PIN) == 0) {
if ((esp_timer_get_time() - start) > 30000) return -1;
}
int64_t echo_start = esp_timer_get_time();
while (gpio_get_level(ECHO_PIN) == 1) {
if ((esp_timer_get_time() - echo_start) > 30000) return -1;
}
int64_t echo_end = esp_timer_get_time();
// Convert µs pulse width to cm
return (int)((echo_end - echo_start) / 58);
}
//TODO11: Create new task for sensor reading every 300ms
void proximity_sensor_task(void *pvParameters) {
ESP_LOGV(TAG, "sensor entered\n");
// HC-SR04 pin setup
gpio_reset_pin(TRIG_PIN);
gpio_set_direction(TRIG_PIN, GPIO_MODE_OUTPUT);
gpio_reset_pin(ECHO_PIN);
gpio_set_direction(ECHO_PIN, GPIO_MODE_INPUT);
TickType_t lastWakeTime = xTaskGetTickCount();
while (1) {
int dist_cm = prox_read_cm();
if (dist_cm < 0) {
ESP_LOGW(TAG, "no echo\n");
} else {
uint16_t sample = (uint16_t) dist_cm;
if (xQueueSend(xSensorQueue, &sample, 0) != pdTRUE) {
ESP_LOGW(TAG, "sensor queue full\n");
} else {
ESP_LOGV(TAG, "sensor queued dist=%u cm\n", sample);
}
}
vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(300));
}
}
// Task: logs when a train is detected in critical zone - hard requirement
void zone_monitor_task(void *pvParameters) {
uint16_t sample;
TickType_t lastWakeTime = xTaskGetTickCount();
while (1) {
uint32_t sum = 0;
int count = 0;
// Variable work — runs 0 to N times depending on queue depth
while (xQueueReceive(xSensorQueue, &sample, 0) == pdTRUE) {
sum += sample;
count++;
}
if (count > 0) {
uint16_t avg = sum / count;
if (avg < SENSOR_THRESHOLD_CM) {
ESP_LOGW(TAG, "TRAIN IN CRITICAL ZONE — avg dist=%u cm\n", avg);
} else {
ESP_LOGI(TAG, "Zone free — avg dist=%u cm\n", avg);
}
vTaskDelay(pdMS_TO_TICKS(600));
}
}
}
// Task: Wait for button, trigger estop warning - hard requirement
void e_stop_logger_task(void *arg) {
while (1) {
// Block until ISR signals button press
if (xSemaphoreTake(xButtonSem, portMAX_DELAY) == pdTRUE) {
if(estopped == false) {
ESP_LOGW(TAG,"ESTOP TRIGGERED\n");
gpio_set_level(RED_LED_PIN, 1);
estopped = true;
}
else {
ESP_LOGW(TAG,"ESTOP RESET\n");
gpio_set_level(RED_LED_PIN, 0);
estopped = false;
}
}
vTaskDelay(pdMS_TO_TICKS(600));
}
}
void app_main() {
gpio_reset_pin(GREEN_LED_PIN);
gpio_set_direction(GREEN_LED_PIN, GPIO_MODE_OUTPUT);
gpio_reset_pin(RED_LED_PIN);
gpio_set_direction(RED_LED_PIN, GPIO_MODE_OUTPUT);
gpio_reset_pin(TRIG_PIN);
gpio_set_direction(TRIG_PIN, GPIO_MODE_OUTPUT);
gpio_reset_pin(ECHO_PIN);
gpio_set_direction(ECHO_PIN, GPIO_MODE_INPUT);
esp_log_level_set(TAG, ESP_LOG_INFO);
// Create synchronization primitives
xSensorQueue = xQueueCreate(LOG_BUFFER_SIZE, sizeof(uint16_t));
xButtonSem = xSemaphoreCreateBinary();
assert(xSensorQueue && xButtonSem);
gpio_install_isr_service(0);
// configuring button with pulldown resistor
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);
// Create tasks
xTaskCreatePinnedToCore(e_stop_logger_task, "ESTOP Pressed", 4096, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(ride_running_task, "Ride ON", 2048, NULL, 1, NULL,1);
xTaskCreatePinnedToCore(print_status_task, "STATUS", 2048, NULL, 1, NULL,1);
xTaskCreatePinnedToCore(proximity_sensor_task, "Proximity Sensor", 4096, NULL,2,NULL,1);
xTaskCreatePinnedToCore(zone_monitor_task, "Zone monitoring", 4096, NULL,2,NULL,1);
}