# HW 3 - Task Design
## Part A - Read a Task Table (20 pts)
### Given Table
| Task | Function | Period (ms) | WCET (ms) | Deadline (ms) |
|------|-------------|-------------|-----------|---------------|
| T1 | sensor read | 10 | 1.0 | 10 |
| T2 | control loop| 20 | 3.0 | 20 |
| T3 | comms send | 50 | 5.0 | 50 |
| T4 | logging | 100 | 10.0 | 100 |
---
### A1 - CPU Fraction
Each task's CPU fraction = WCET / Period:
| Task | Calculation | Utilization |
|------|-------------|-------------|
| T1 | 1.0 / 10 | 0.100 |
| T2 | 3.0 / 20 | 0.150 |
| T3 | 5.0 / 50 | 0.100 |
| T4 | 10.0 / 100 | 0.100 |
| **Total** | | **0.450** |
Total CPU utilization is **45.0%**. Idle fraction remaining: **55.0%**.
Note: this is plain utilization arithmetic. The formal schedulability test (Liu & Layland RMS bound, EDF feasibility) is covered in Week 4 / HW 4.
---
### A2 - Priority Ranking
**Rule: Rate Monotonic Scheduling (RMS) - shorter period = higher priority.**
Ranked highest to lowest priority:
1. T1 - sensor read (period 10 ms) - highest priority
2. T2 - control loop (period 20 ms)
3. T3 - comms send (period 50 ms)
4. T4 - logging (period 100 ms) - lowest priority
**Priority band mapping** (0 idle, 1-4 housekeeping, 5-9 user, 10-14 sensor/IPC, 15-18 control, 19-24 system):
| Task | Function | Assigned Priority | Band | Justification |
|------|-------------|-------------------|-----------------|---------------|
| T1 | sensor read | 12 | sensor/IPC (10-14) | Raw sensor acquisition; feeds the control loop downstream |
| T2 | control loop| 16 | control (15-18) | Closed-loop controller; function semantics place it in control band regardless of longer period |
| T3 | comms send | 7 | user (5-9) | Outbound telemetry; no hard consequence to a late packet |
| T4 | logging | 2 | housekeeping (1-4) | Background storage; best-effort, no safety consequence |
---
### A3 - Yield Semantics for T2
**T2 should use `vTaskDelayUntil`, not `vTaskDelay`.**
`vTaskDelay(N)` sleeps for N ticks measured from when the call is made. The next wake time shifts forward by however long the task body actually ran. Over many cycles this accumulates **drift**: the effective period grows beyond 20 ms and the phase relative to T1's sensor output slides unpredictably. A control loop sampling on a drifting schedule introduces **phase error** - the control action is computed against data that is increasingly stale or mis-timed relative to the plant dynamics.
`vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(20))` pins the wake time to an absolute tick count, so the period stays exactly 20 ms regardless of WCET variation within the window. Drift is eliminated and phase error stays bounded to sub-tick jitter. For a closed-loop controller this is the correct primitive.
---
### A4 - FreeRTOS State-Machine Trace for T1
Starting point: T1 has just finished a sensor read (about to block for next period).
| Step | State | Event / API call |
|------|----------|-----------------|
| 1 | Running | Sensor read body completes; task calls `vTaskDelayUntil()` |
| 2 | Blocked | Kernel moves T1 to the delay list; next task takes CPU |
| 3 | Blocked | Tick ISR fires each ms; kernel checks delay list each tick |
| 4 | Ready | Tick ISR unblocks T1 (delay expired); T1 placed on ready list |
| 5 | Running | Scheduler context-switches to T1 (highest priority ready task) |
T1 is Running again and begins the next sensor read. Full cycle complete.
---
## Part B - xTaskCreate Defended (25 pts)
```c
xTaskCreate(
vControlLoop, /* pvTaskCode - task function pointer */
"CTRL_LOOP", /* pcName - debug name */
256, /* usStackDepth - stack size in 32-bit words */
NULL, /* pvParameters - startup argument */
16, /* uxPriority - scheduling priority */
&xControlLoopHandle /* pxCreatedTask - handle for external control */
);
```
**Parameter defenses:**
1. **`vControlLoop` (pvTaskCode)** - This is the function pointer to T2's control loop body. FreeRTOS jumps to this address on first dispatch; the function must never return and wraps its body in an infinite loop calling `vTaskDelayUntil` at the bottom.
2. **`"CTRL_LOOP"` (pcName)** - A human-readable string stored in the TCB for debugging via `vTaskList()` and JTAG/OpenOCD trace tools. It has no scheduling effect; kept under 16 characters to stay within `configMAX_TASK_NAME_LEN`.
3. **`256` (usStackDepth, in 32-bit words = 1 KB)** - I estimated worst-case stack depth as: task prologue (~16 words) + PID math with floats (~32 words) + called utility functions (~64 words) + IRQ nesting margin (~64 words) = ~176 words minimum. Rounded up to 256 for safety headroom. On ESP32 with 512 KB SRAM this is negligible cost.
4. **`NULL` (pvParameters)** - T2 takes no runtime-configurable parameters; its setpoint and sensor handle are accessed through statically-scoped globals or a config struct visible at link time. Passing NULL avoids a dangling pointer risk and keeps the signature clean.
5. **`16` (uxPriority)** - Assigned to the control band (15-18) per the RMS band mapping from Part A. Priority 16 places T2 above comms and logging but below any future interrupt-proxying task in the system band, correctly reflecting its role as the primary control actor.
6. **`&xControlLoopHandle` (pxCreatedTask)** - Retaining the handle allows a supervisor or watchdog task to call `eTaskGetState(xControlLoopHandle)` to verify T2 is still alive, and `vTaskDelete(xControlLoopHandle)` on a detected fault, without searching by name at runtime.
---
## Part C - Design Your Own: Medical Theme (35 pts)
**System: Continuous Patient Monitoring Unit**
Monitors SpO2 and heart rate from a pulse oximeter (MAX30102 over I2C), evaluates vitals against alarm thresholds, and logs records to flash storage.
### Task Table
| Task | Name | Purpose | Period (ms) | WCET (ms) | Deadline (ms) | Priority |
|------|---------------|------------------------------------------------------|-------------|-----------|---------------|----------|
| TM1 | `vSpO2Sample` | Read SpO2 + HR from MAX30102 sensor over I2C | 20 | 2.0 | 20 | 15 |
| TM2 | `vAlarmEval` | Compare latest vitals to thresholds; drive buzzer/LED | 40 | 4.0 | 40 | 12 |
| TM3 | `vVitalLog` | Write timestamped vitals record to flash via SPI | 500 | 20.0 | 500 | 3 |
**Utilization check:**
- TM1: 2.0 / 20 = 0.100
- TM2: 4.0 / 40 = 0.100
- TM3: 20.0 / 500 = 0.040
- **Total: 0.240 (24%) - well within budget**
### Priority Justifications
- **TM1, priority 15 (control band):** Shortest period; patient data must be sampled on a deterministic schedule to maintain signal integrity. A missed sample creates a gap the alarm evaluator cannot reason about. RMS assigns it the highest priority.
- **TM2, priority 12 (sensor/IPC band):** Alarm evaluation is time-sensitive - a missed deadline means a dangerous event goes unannounced for a full extra 40 ms cycle - but TM2 depends on TM1 completing first, so it sits one band lower. The 40 ms deadline is still inside clinically acceptable alarm latency.
- **TM3, priority 3 (housekeeping band):** Logging is best-effort. A late write does not affect patient safety directly; records only need to be eventually written. Lowest priority is correct; flash writes absorb jitter without consequence.
### Shared Resource
**Resource:** `xVitalsBuffer` - a struct holding the latest SpO2 percentage and heart rate values.
**Producer:** TM1 writes to `xVitalsBuffer` after each successful sensor read.
**Consumer:** TM2 reads from `xVitalsBuffer` before each alarm evaluation.
**Guard:** `xVitalsMutex` - a FreeRTOS mutex with priority inheritance enabled (default). TM1 takes the mutex before writing and releases it after. TM2 takes the mutex before reading and releases it after. Priority inheritance prevents a lower-priority task from blocking the mutex and causing TM1 to miss its deadline.
### Concurrency Diagram
The diagram shows two full periods of TM1 (0-40 ms) and one period of TM2 (0-40 ms). TM3 has a 500 ms period and does not appear in this window.
```mermaid
gantt
title Patient Monitor - Task Concurrency (0 to 80 ms)
dateFormat X
axisFormat %Lms
section TM1 vSpO2Sample (P=15)
Run + write mutex :active, tm1a, 0, 2
Blocked (vTaskDelayUntil) :done, 2, 20
Run + write mutex :active, tm1b, 20, 22
Blocked (vTaskDelayUntil) :done, 22, 40
Run + write mutex :active, tm1c, 40, 42
Blocked (vTaskDelayUntil) :done, 42, 60
Run + write mutex :active, tm1d, 60, 62
Blocked (vTaskDelayUntil) :done, 62, 80
section TM2 vAlarmEval (P=12)
Waiting for TM1 :done, 0, 2
Run + read mutex :active, tm2a, 2, 6
Blocked (vTaskDelayUntil) :done, 6, 42
Waiting for TM1 :done, 42, 42
Run + read mutex :active, tm2b, 42, 46
Blocked (vTaskDelayUntil) :done, 46, 80
section TM3 vVitalLog (P=3)
Blocked (vTaskDelayUntil, fires at 500ms) :done, 0, 80
section xVitalsMutex
TM1 holds (write) :crit, m1, 0, 2
TM2 holds (read) :crit, m2, 2, 3
TM1 holds (write) :crit, m3, 20, 22
TM2 holds (read) :crit, m4, 42, 43
```
**Key observations:**
- TM1 preempts everything at t=0, 20, 40, 60 ms (highest priority). It holds `xVitalsMutex` only for the brief write at the end of each run (~1 ms), then releases before blocking.
- TM2 wakes at t=0 but immediately yields to TM1 (lower priority). It acquires `xVitalsMutex` at t=2 ms to read the freshly written vitals buffer, then runs its threshold evaluation until t=6 ms.
- This producer/consumer ordering is guaranteed by priority: TM1 always writes before TM2 can read within the same 40 ms window.
- TM3 remains blocked for the entire 80 ms window shown. It fires at t=500 ms and runs uncontested since TM1 and TM2 are both sleeping at that point.
- Priority inheritance on `xVitalsMutex` ensures that if TM3 ever held the mutex (it does not access `xVitalsBuffer` in this design), the kernel would temporarily boost TM3 to TM1's priority to prevent deadline inversion.
---
## Part D - Industry Anchor: AUTOSAR Classic (20 pts)
AUTOSAR Classic uses a **fixed-priority preemptive scheduler** built on the OSEK/VDX OS specification, which defines tasks, alarms, events, and resources under a fully static configuration model. Every task priority, period, and resource ceiling protocol is determined at build time using an XML-based toolchain - there is no dynamic task creation at runtime. AUTOSAR Classic is pervasive in automotive ECUs: engine control modules, ABS/ESC units, transmission controllers, and body control modules running on microcontrollers such as Infineon TriCore and Renesas RH850. The most significant difference from FreeRTOS is the **static-only task model**: FreeRTOS allows `xTaskCreate` and `vTaskDelete` at runtime, while AUTOSAR Classic forbids runtime task creation entirely. This is a deliberate safety constraint - because the task set is fixed at compile time, the scheduler's worst-case behavior can be fully analyzed offline with tools like SymTA/S or Timing Architects, which is required for ISO 26262 ASIL-D certification. FreeRTOS offers runtime flexibility; AUTOSAR Classic trades that flexibility for provability.