/* --------------------------------------------------------------
Application: 06
Class: Real Time Systems - Su 2025
Author: Skeleton Provided by Dr. Mike, edited by Caden Kohlmeier
Email: [email protected]
AI Use: AI commented in-line. Also used for validation and assessing problems.
Available Semaphore: https://www.freertos.org/Documentation/02-Kernel/04-API-references/10-Semaphore-and-Mutexes/00-Semaphores
Theme: Health Care Heart Rate Monitoring System: Track “heart rate” analog input; push an emergency alert button to request intervention.
---------------------------------------------------------------*/
#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"
//Physical components and GPIO Pins
#define LED_GREEN GPIO_NUM_5 //green led system status
#define LED_RED GPIO_NUM_4 //red led heartrate zone
#define BUTTON_PIN GPIO_NUM_18 // emergency alert
#define POT_ADC_CHANNEL ADC1_CHANNEL_6 // GPIO34 ADC input
#define AVG_WINDOW 10
#define MAX_COUNT_SEM 200 //150 is precise, but including a buffer of ~50 will guarantee it will not miss a high frequency event
#define SENSOR_THRESHOLD 1800 //recommended threshold by Claude
#define LOG_BUFFER_SIZE 50
//Handles for semaphores and mutex
SemaphoreHandle_t sem_HB_sensor;
SemaphoreHandle_t print_mutex;
SemaphoreHandle_t sem_button;
SemaphoreHandle_t xButtonSem;
SemaphoreHandle_t xLogMutex;
static TickType_t last_button_press = 0; //last button press for debounce
volatile int SEMCNT = 0; //You may not use this value in your logic -- but you can print it if you wish
//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
const static char *TAG = "FL Hospital Patient Monitoring System";
//Task 1: Blink Green LED to show system ON - Priority 1
void heartbeat_task(void *pvParameters) {
bool green_led_state = false;
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
gpio_set_level(LED_GREEN, green_led_state);
green_led_state = !green_led_state;
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
}
}
//Task 2: Print Raw Sensor Value - Priority 2
void sensor_task(void *pvParameters) {
int pot_readings[AVG_WINDOW] = {0};
int idx = 0;
int sum = 0;
static bool above_thresh = false;
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
int val = adc1_get_raw(POT_ADC_CHANNEL);
//static variable retained through program run
sum -= pot_readings[idx];
pot_readings[idx] = val;
sum += val;
idx = (idx+1) % AVG_WINDOW;
int avg = sum / AVG_WINDOW;
static TickType_t xPrintTime = 0; // Print once per second to avoid clutter
if (xTaskGetTickCount() - xPrintTime >= pdMS_TO_TICKS(1000)) {
if (xSemaphoreTake(print_mutex, portMAX_DELAY)) {
printf("[%lu ms] Heart Rate (ADC) %d\n", xTaskGetTickCount() * portTICK_PERIOD_MS, val); //time stamp logic generated by AI
xSemaphoreGive(print_mutex);
}
xPrintTime = xTaskGetTickCount();
}
if (val > SENSOR_THRESHOLD && !above_thresh) { //if raw value is above sensorthresh and above_thresh is not false (true)
above_thresh = true; //then above threshold is true
if(SEMCNT < MAX_COUNT_SEM+1) SEMCNT++; // DO NOT REMOVE THIS LINE... SEMCT: global counter that tracks # of given, MAX_COUNT_SEM: max capacity of 10 and prevent semaphore overflow
xSemaphoreGive(sem_HB_sensor); // Signal
}else if (val<= SENSOR_THRESHOLD){
above_thresh = false;
}
static TickType_t xAlertTime =0; //procedure taken from line 77
if (avg >= SENSOR_THRESHOLD && (xTaskGetTickCount() - xAlertTime >= pdMS_TO_TICKS(1000))) {
if (xSemaphoreTake(print_mutex, portMAX_DELAY)) {
uint32_t now_ms = xTaskGetTickCount() * portTICK_PERIOD_MS;
printf("[%lu ms] ALERT: Exceeded Threshold Rate\n", now_ms);
xSemaphoreGive(print_mutex);
}
xAlertTime = xTaskGetTickCount();
}
if (xSemaphoreTake(xLogMutex, pdMS_TO_TICKS(10))) {
sensor_log[log_index] = (uint16_t)val; //sensor_log[log_index] = (uint16_t)lux; claude AI redirected me to define as
log_index = (log_index + 1) % LOG_BUFFER_SIZE;
readings_count++;
xSemaphoreGive(xLogMutex);
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(100)); //vTaskDelay(pdMS_TO_TICKS(1)); will dramatically starve the green LED
}
}
//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(100)) { // 100ms debounce
BaseType_t xHigherWoken = pdFALSE;
xSemaphoreGiveFromISR(xButtonSem, &xHigherWoken);
portYIELD_FROM_ISR(xHigherWoken);
last_button_press = now;
}
}
void event_handler_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(sem_HB_sensor, 0)) {
SEMCNT--; // DO NOT MODIFY THIS LINE
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("Report to Patient\n");
xSemaphoreGive(print_mutex);
gpio_set_level(LED_RED, 1);
vTaskDelay(pdMS_TO_TICKS(5000));
gpio_set_level(LED_RED, 0);
}
if (xSemaphoreTake(sem_button, 0)) {
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("Emergency Alert Button Activated\n");
xSemaphoreGive(print_mutex);
gpio_set_level(LED_RED, 1);
vTaskDelay(pdMS_TO_TICKS(300)); //vTaskDelay(pdMS_TO_TICKS(3000)); //increase the delay to 3s to miss button presses
gpio_set_level(LED_RED, 0);
}
vTaskDelay(pdMS_TO_TICKS(10)); // Idle delay to yield CPU
}
}
//logger task compress and dump
void logger_task(void *arg) {
while (1) {
//block until 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 min, max, avg
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;
//log dump print
printf("\nVitals Report Requested\n");
printf("Total readings processed: %d\n", effective_readings);
printf("Heart Rate Sensor Stats(ADC):\n Minimum = %d\n Maximum = %d\n Average = %d\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(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 input 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 ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(POT_ADC_CHANNEL, ADC_ATTEN_DB_11);
//Sync primitives
sem_button = xSemaphoreCreateBinary();
sem_HB_sensor = xSemaphoreCreateCounting(MAX_COUNT_SEM,0);
print_mutex = xSemaphoreCreateMutex();
xButtonSem = xSemaphoreCreateBinary();
xLogMutex = xSemaphoreCreateMutex();
assert(xLogMutex && xButtonSem); //defensive practice to validate xlogmutex and xbuttonsem
//ISR Service
ESP_LOGI(TAG, "Configuring button interrupt on GPIO%d", BUTTON_PIN);
gpio_set_intr_type(BUTTON_PIN, GPIO_INTR_NEGEDGE);
gpio_install_isr_service(0);
gpio_isr_handler_add(BUTTON_PIN, button_isr_handler, NULL);
// Create tasks
xTaskCreate(heartbeat_task, "heartbeat", 2048, NULL, 1, NULL); //soft RT requirement, uses vTaskDelayUntil for absolute timing to avoid drift. On 1000ms trigger and has a soft deadline
xTaskCreate(sensor_task, "sensor", 2048, NULL, 3, NULL); //responds every 100ms hard deadline because this is detecting anomalies in patient vitals. Failure to see these can result in death
xTaskCreate(event_handler_task, "event_handler", 2048, NULL, 4, NULL); //hard RT requirement to process alerts
xTaskCreate(logger_task, "data", 2048, NULL, 2, NULL); //soft RT requirement, no deadline
//ISR: HARD deadline. Takes priority over all tasks and MUST be accounted for. Can be pressed by patient to request emergency assistance or a nurse to check vitals. Also operates on debounce
ESP_LOGI(TAG, "Vitals Monitoring System Active");
}