/* --------------------------------------------------------------
Application: 04
Release Type: Use of Memory Based Task Communication
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 "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!
//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
// Handles for semaphores and mutex
SemaphoreHandle_t sem_HB_sensor;
SemaphoreHandle_t print_mutex;
SemaphoreHandle_t sem_button;
volatile int SEMCNT = 0; //You may not use this value in your logic -- but you can print it if you wish
//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;
if (xSemaphoreTake(print_mutex, portMAX_DELAY)){
printf("Raw Value: %d\n", val);
xSemaphoreGive(print_mutex);
}
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;
}
if (avg >= SENSOR_THRESHOLD) {
if (xSemaphoreTake(print_mutex, portMAX_DELAY)) {
printf("ALERT: Current HB Average of %d exceeds a Threshold Value of %d\n", avg, SENSOR_THRESHOLD);
xSemaphoreGive(print_mutex);
}
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(100)); //vTaskDelay(pdMS_TO_TICKS(1)); will dramatically starve the green LED
}
}
//Task 3: Button Task That signals when a button is pressed and a debounce is executed (100ms)
void button_task(void *pvParameters) {
static TickType_t prev_button_press = 0;
static bool last_button_state = true; //Generated by Claude for a true single button press design; track previous state
while (1) {
int state = gpio_get_level(BUTTON_PIN);
if (state == 0 && last_button_state == true){ //if (state == 0){
TickType_t now = xTaskGetTickCount();
if ((now - prev_button_press) > pdMS_TO_TICKS(100)){
prev_button_press = now;
xSemaphoreGive(sem_button); //signal event
}
}
last_button_state = state; //generated by Claude for true single button press; update state tracker
vTaskDelay(pdMS_TO_TICKS(10)); // Do Not Modify This Delay!
}
}
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("Sensor event: Threshold Rate Has Been Exceeded!\n");
xSemaphoreGive(print_mutex);
gpio_set_level(LED_RED, 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(LED_RED, 0);
}
if (xSemaphoreTake(sem_button, 0)) {
xSemaphoreTake(print_mutex, portMAX_DELAY);
printf("Emergency Alert Button Activated-Report To Patient Immediately\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
}
}
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();
// Create tasks
xTaskCreate(heartbeat_task, "heartbeat", 2048, NULL, 1, NULL);
xTaskCreate(sensor_task, "sensor", 2048, NULL, 2, NULL);
xTaskCreate(button_task, "button", 2048, NULL, 4, NULL);
xTaskCreate(event_handler_task, "event_handler", 2048, NULL, 3, NULL);
}
/*
Completed Tasks
TODO 0a: connect the components in the diagram to the GPIO pins listed below.
TODO 0b: Set heartbeat to cycle once per second (on for one second, off for one second)
TODO 0c: Attach the three SemaphoreHandle_t defined earlier
-(sem_button, sem_HB_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);
TODO 1: Adjust threshold based on your scenario or input testing
-Currently set to 2048 to put at 50% scale
TODO 2: Add serial print to log the raw sensor value (mutex protected)
TODO 3: prevent spamming by only signaling on rising edge; See prior application #3 for help!
-Logic: Signal Once on rising edge above 2048. If it stays above 2048, do not continuously signal. If < 2048, then >2048, signal.
TODO 4a: Add addtional logic to prevent bounce effect (ignore multiple events for 'single press')
TODO 4b: Add a console print indicating button was pressed (mutex protected); different message than in event handler
TODO 5: Test removing the print_mutex around console output (expect interleaving)
-Observe console when two events are triggered close together
Testing for TODO5:
*Removed print_mutex around all console outputs
*Result: I was not able to press the button and trigger the sensor threshold close enough to see an observable interleaving. Interleaving may be acheieved if the print statements were much longer
TODO 6: Experiment with changing task priorities to induce or fix starvation
-I experimented several ways with the task priorities.
Starved the green LED setting it to priority 1 and the rest to priority 4. It looked like it slowed down slightly.
Setting the green LED at the highest 5 and the rest lower priority starved the red LED from flashing when I rapidly toggled the potentiometer threshold.
-Given setting are very functional. My implementation I valued the button task.
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?
Counting semaphore max size: 10
Sensor Task delay: 100ms, but there is a threshold crossing so its really 200ms
30000/(100*2) = 150 cycle + buffer
*/