/* --------------------------------------------------------------
Application: 03
Class: Real Time Systems - Su 2025
Caden Kohlmeier
[email protected]
DUE: 6/21/25
AI Use: A mix of claude AI and ChatGPT were used for debugging along with code generation. Referencing the book and the parameters of Applictaion 3, AI assisted with ensuring specifications were met.
Additionally, AI was used to verify all components of the analysis questions were met and satisfactory. Initial questions were made in a word document then AI converted to an approprriate markdown format.
---------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "math.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#define LED_PIN GPIO_NUM_2
#define LDR_PIN GPIO_NUM_32
#define LDR_ADC_CHANNEL ADC1_CHANNEL_4
#define AVG_WINDOW 10
#define SENSOR_THRESHOLD 200
#define BUTTON_PIN GPIO_NUM_4
#define LOG_BUFFER_SIZE 50
const float GAMMA = 0.7;
const float RL10 = 50;
const static char *TAG = "Application 3";
//Shared Buffer and index
static uint16_t sensor_log[LOG_BUFFER_SIZE];
static volatile int log_index = 0;
static volatile int readings_count = 0; //total readings
// Synchronization
static SemaphoreHandle_t xLogMutex; // Hand off access to the buffer
static SemaphoreHandle_t xButtonSem; // Hand off on button press
static TickType_t last_button_press = 0; //last button press for debounce
//ISR Triggered By Button Press with debounce
void IRAM_ATTR button_isr_handler(void* arg) {
TickType_t now = xTaskGetTickCountFromISR();
if ((now - last_button_press) > pdMS_TO_TICKS(50)) { // 50ms debounce
BaseType_t xHigherWoken = pdFALSE;
xSemaphoreGiveFromISR(xButtonSem, &xHigherWoken);
portYIELD_FROM_ISR(xHigherWoken);
last_button_press = now;
}
}
//Blink an LED 1400ms
void led_task(void *pvParameters) {
bool led_on = false;
while (1) {
gpio_set_level(LED_PIN, led_on);
led_on = !led_on;
vTaskDelay(pdMS_TO_TICKS(1400));
}
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() );
// Prints message/7 second. Output a timestamp (ms) and period (ms)
printf("Patient Monitor Running at %lums [period = %lu]\n",currentTime, currentTime-previousTime);
vTaskDelay(pdMS_TO_TICKS(7000));
}
vTaskDelete(NULL); // We'll never get here; tasks run forever
}
//Sensor task reading for every 500ms (0.5s) and store in buffer. Button press will print
void sensor_task(void *pvParameters) {
//Configure ADC (12-bit width, 0-3.3V range with 11dB attenuation)
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(LDR_ADC_CHANNEL, ADC_ATTEN_DB_11);
// Variables to compute LUX
int raw; //raw adc 0-4095
float Vmeasure = 0.; //calculated voltage from adc raw value
float Rmeasure = 0.; //resistance of ldr
float lux = pow(RL10 * 1e3 * pow(10, GAMMA) / Rmeasure, (1 / GAMMA)); // final lux value
// Variables for moving average
int luxreadings[AVG_WINDOW] = {0};
int 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 = ((float)raw / 4095) * 3.3;
Rmeasure = (Vmeasure * 10000) / (1 - (Vmeasure / 3.3));
lux = pow(RL10 * 1e3 * pow(10, GAMMA) / Rmeasure, (1 / GAMMA));
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 ADC: %d\nVoltage: %.2fV\n", raw, Vmeasure);
// Compute LUX
Vmeasure = ((float)raw / 4095) * 3.3;
Rmeasure = (Vmeasure * 10000) / (1 - (Vmeasure / 3.3));
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
//Check threshold and print alert if exceeded or below based on context
if (avg >= SENSOR_THRESHOLD) {
printf("Alert: Current avg of %d exceeds threshold avg of %d\n", avg, SENSOR_THRESHOLD);
} else {
printf("Heart rate threshold not exceeded, continue workout\n");
}
if (xSemaphoreTake(xLogMutex, pdMS_TO_TICKS(10))) {
sensor_log[log_index] = (uint16_t)lux;
log_index = (log_index + 1) % LOG_BUFFER_SIZE;
readings_count++;
xSemaphoreGive(xLogMutex);
}
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,"Button Pressed, Process will execute\n");
uint16_t copy[LOG_BUFFER_SIZE];
int current_count = 0;
int current_index = 0;
// Lock mutex to read from buffer
if (xSemaphoreTake(xLogMutex, pdMS_TO_TICKS(100))) {
memcpy(copy, sensor_log, sizeof(copy));
current_count = readings_count;
current_index = log_index;
xSemaphoreGive(xLogMutex);
}
// Simulate compression by calculating comprehensive statistics
uint16_t min = 4095, max = 0;
uint32_t sum = 0;
int above_threshold = 0;
int effective_readings = (current_count < LOG_BUFFER_SIZE) ? current_count : LOG_BUFFER_SIZE;
// Process only valid readings
for (int i = 0; i < effective_readings; i++) {
uint16_t value = copy[i];
if (value < min) min = value;
if (value > max) max = value;
sum += value;
if (value > SENSOR_THRESHOLD) above_threshold++;
}
if (effective_readings > 0) {
uint16_t avg = sum / effective_readings;
// "Compressed" log dump with statistics
printf("\nVitals Report Requested\n");
printf("Total readings processed: %d\n", effective_readings);
printf("Light sensor stats: min=%d, max=%d, avg=%d lux\n", min, max, avg);
printf("Readings above threshold (%d): %d (%.1f%%)\n",
SENSOR_THRESHOLD, above_threshold,
(float)above_threshold * 100.0 / effective_readings);
printf("Buffer utilization: %d/%d slots\n", effective_readings, LOG_BUFFER_SIZE);
printf("End Report\n\n");
if (xSemaphoreTake(xLogMutex, pdMS_TO_TICKS(100))) {
memset(sensor_log, 0, sizeof(sensor_log));
log_index = 0;
readings_count = 0;
xSemaphoreGive(xLogMutex);
ESP_LOGI(TAG, "Sensor log buffer cleared");
}
} else {
printf("No sensor data available for compression\n");
}
}
}
}
void app_main() {
// 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);
// Create synchronization objects
xButtonSem = xSemaphoreCreateBinary();
xLogMutex = xSemaphoreCreateMutex();
assert(xLogMutex && xButtonSem);
// Install ISR service
gpio_install_isr_service(0);
// Configure button with interrupt
ESP_LOGI(TAG, "Configuring button interrupt on GPIO%d", BUTTON_PIN);
gpio_reset_pin(BUTTON_PIN);
gpio_set_direction(BUTTON_PIN, GPIO_MODE_INPUT);
gpio_pullup_en(BUTTON_PIN);
gpio_set_intr_type(BUTTON_PIN, GPIO_INTR_NEGEDGE);
gpio_isr_handler_add(BUTTON_PIN, button_isr_handler, NULL);
// Configure ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(LDR_ADC_CHANNEL, ADC_ATTEN_DB_11);
// Create all tasks pinned to Core 1 with specified priorities
ESP_LOGI(TAG, "Creating tasks...");
xTaskCreatePinnedToCore(led_task, "LEDTask", 2048, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(print_task, "PrintTask", 2048, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(sensor_task, "SensorTask", 4095, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(logger_task, "LoggerTask", 4095, NULL, 3, NULL, 1);
ESP_LOGI(TAG, "All tasks created and running on Core 1");
}
/* App 3 Requirements
1. LED blink task
*Toggled LED on and off at 1.4s to simulate visual heartbeat- done
*Must remain a low priority task - done
*turn off any print or logging with the blink task - done
2. Console Print task
*Print message every 7 seconds. Message can be status info, sensor reading, telemetry but keep thematic -
*Can display light sensor current value or debug info
* Low/ medium priority
3. Light Sensor Task
*Read light sensor periodically (0.1s-0.5s)
*store readings in a buffer (log) instead of just printing
-choose buffer size (array of last 50-100 readings)
-each cycle read analogRead and store in array
-possible maintain count of how many readings have been taken or wrapping
-print reading (optional), but populate data that the new task will process on demand
-medium priority > blink/print (sensor sampng is important)
4. Button Trigger (new task)
*Task will waitr for a button press event then execute special action
*simulate compressing collected sensor log and dumping it via serial output
-start by creating binary sempaphore. Initialize as empty/take at start to start in blocking state
-task will call xSemaphoreTake(buttonSem, portMAX_DELAY) to block itself until semaphore given by ISR
> task will sleep until button press
-In ISR, (GPIO falling edge) use xSemapgoreGiveFromISR() to signal semaphore, pass pxHigherPriorityTaskWoken to indicate yield should occur
>wakes logger task immediately when ISR completes
*Logger task runs when unblocked
-compress sensor data log and print it
*/