/*
Real-Time Automotive ECU Simulator
STM32 Nucleo-C031C6 + FreeRTOS (Wokwi Simulation)
-- Flash-optimized version: no LCD/I2C, no floating point --
Tasks:
1. SensorAcquisitionTask - reads 3 potentiometers (temp, RPM, battery)
2. VehicleMonitoringTask - builds the vehicle status from raw readings
3. FaultDetectionTask - checks thresholds + handles emergency button
4. DashboardTask - prints everything to the Serial Monitor
Inter-task communication:
xSensorQueue : SensorAcquisitionTask -> VehicleMonitoringTask
xMonitorQueue : VehicleMonitoringTask -> FaultDetectionTask
xDashboardQueue : FaultDetectionTask -> DashboardTask
Synchronization:
xEmergencySemaphore (binary) : button ISR -> FaultDetectionTask
xSerialMutex : protects the shared Serial resource
*/
#include <STM32FreeRTOS.h>
// ---------------- Pin definitions ----------------
#define PIN_TEMP_POT A0
#define PIN_RPM_POT A1
#define PIN_BATT_POT A2
#define PIN_BUTTON D2
#define PIN_WARN_LED D5
// ---------------- Fault thresholds ----------------
#define TEMP_MAX_C 110
#define RPM_MAX 6500
#define BATT_MIN_TENTHS 115 // 11.5 V, stored as tenths of a volt
// ---------------- Data structures ----------------
typedef struct {
int tempC;
int rpm;
int battTenths; // battery voltage x10 (e.g. 123 = 12.3 V)
} SensorData_t;
typedef struct {
int tempC;
int rpm;
int battTenths;
bool tempFault;
bool rpmFault;
bool battFault;
bool emergency;
} VehicleStatus_t;
// ---------------- RTOS objects ----------------
QueueHandle_t xSensorQueue;
QueueHandle_t xMonitorQueue;
QueueHandle_t xDashboardQueue;
SemaphoreHandle_t xEmergencySemaphore;
SemaphoreHandle_t xSerialMutex;
// ---------------- ISR: emergency button ----------------
void emergencyButtonISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xEmergencySemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// ---------------- Task 1: Sensor Acquisition ----------------
void SensorAcquisitionTask(void *pvParameters) {
SensorData_t data;
for (;;) {
int rawTemp = analogRead(PIN_TEMP_POT);
int rawRpm = analogRead(PIN_RPM_POT);
int rawBatt = analogRead(PIN_BATT_POT);
data.tempC = map(rawTemp, 0, 4095, 20, 130); // 20-130 C
data.rpm = map(rawRpm, 0, 4095, 0, 8000); // 0-8000 RPM
data.battTenths = map(rawBatt, 0, 4095, 90, 150); // 9.0-15.0 V (x10)
xQueueSend(xSensorQueue, &data, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// ---------------- Task 2: Vehicle Monitoring ----------------
void VehicleMonitoringTask(void *pvParameters) {
SensorData_t data;
VehicleStatus_t status;
for (;;) {
if (xQueueReceive(xSensorQueue, &data, portMAX_DELAY) == pdTRUE) {
status.tempC = data.tempC;
status.rpm = data.rpm;
status.battTenths = data.battTenths;
status.tempFault = (data.tempC > TEMP_MAX_C);
status.rpmFault = (data.rpm > RPM_MAX);
status.battFault = (data.battTenths < BATT_MIN_TENTHS);
status.emergency = false; // filled in by FaultDetectionTask
xQueueSend(xMonitorQueue, &status, portMAX_DELAY);
}
}
}
// ---------------- Task 3: Fault Detection ----------------
void FaultDetectionTask(void *pvParameters) {
VehicleStatus_t status;
static bool emergencyLatched = false;
static uint8_t emergencyCounter = 0;
for (;;) {
if (xQueueReceive(xMonitorQueue, &status, pdMS_TO_TICKS(600)) == pdTRUE) {
if (xSemaphoreTake(xEmergencySemaphore, 0) == pdTRUE) {
emergencyLatched = true;
emergencyCounter = 6; // keep the emergency shown for ~3.6 s
}
if (emergencyLatched) {
status.emergency = true;
if (emergencyCounter > 0) emergencyCounter--;
else emergencyLatched = false;
}
bool fault = status.tempFault || status.rpmFault ||
status.battFault || status.emergency;
digitalWrite(PIN_WARN_LED, fault ? HIGH : LOW);
xQueueSend(xDashboardQueue, &status, 0);
}
}
}
// ---------------- Task 4: Dashboard (Serial Monitor) ----------------
void DashboardTask(void *pvParameters) {
VehicleStatus_t status;
for (;;) {
if (xQueueReceive(xDashboardQueue, &status, portMAX_DELAY) == pdTRUE) {
if (xSemaphoreTake(xSerialMutex, pdMS_TO_TICKS(200)) == pdTRUE) {
Serial.println(F("---------------------------"));
Serial.print(F("Temp : ")); Serial.print(status.tempC); Serial.println(F(" C"));
Serial.print(F("RPM : ")); Serial.println(status.rpm);
Serial.print(F("Batt : ")); Serial.print(status.battTenths / 10);
Serial.print('.'); Serial.print(status.battTenths % 10); Serial.println(F(" V"));
if (status.emergency) {
Serial.println(F(">>> EMERGENCY <<<"));
} else if (status.tempFault || status.rpmFault || status.battFault) {
Serial.println(F("*** FAULT DETECTED ***"));
if (status.tempFault) Serial.println(F(" - Overheat"));
if (status.rpmFault) Serial.println(F(" - Over-rev"));
if (status.battFault) Serial.println(F(" - Low battery"));
} else {
Serial.println(F("Status: NORMAL"));
}
xSemaphoreGive(xSerialMutex);
}
}
}
}
// ---------------- setup / loop ----------------
void setup() {
analogReadResolution(12); // use full 12-bit ADC range (0-4095) to match map()
Serial.begin(115200);
pinMode(PIN_WARN_LED, OUTPUT);
digitalWrite(PIN_WARN_LED, LOW);
pinMode(PIN_BUTTON, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), emergencyButtonISR, FALLING);
Serial.println(F("ECU Simulator booting..."));
xSensorQueue = xQueueCreate(5, sizeof(SensorData_t));
xMonitorQueue = xQueueCreate(5, sizeof(VehicleStatus_t));
xDashboardQueue = xQueueCreate(5, sizeof(VehicleStatus_t));
xEmergencySemaphore = xSemaphoreCreateBinary();
xSerialMutex = xSemaphoreCreateMutex();
xTaskCreate(SensorAcquisitionTask, "Sensor", 128, NULL, 2, NULL);
xTaskCreate(VehicleMonitoringTask, "Monitor", 128, NULL, 2, NULL);
xTaskCreate(FaultDetectionTask, "Fault", 128, NULL, 3, NULL);
xTaskCreate(DashboardTask, "Dashboard", 128, NULL, 1, NULL);
vTaskStartScheduler(); // never returns
}
void loop() {
// Not used - FreeRTOS scheduler drives everything from setup()
}Loading
st-nucleo-c031c6
st-nucleo-c031c6