Initial commit: demo-epb v1.0 — Elektrische Parkbremse Demo

Vollstaendige Demo des slohmaier Dev Process anhand einer EPB-Steuergeraet-
Software. Zeigt ASPICE 4.0 / ISO 26262-konforme Entwicklung im Monorepo.

Inhalte:
- 5 Plaene (PID, PM-, QA-, SWE-, Test-Plan) in Word, ausgefuellt mit
  EPB-spezifischen Inhalten
- 10 System-Anforderungen + 25 Software-Anforderungen (Doorstop-MD)
- 5 System-Architektur-Elemente + 10 Software-Architektur-Elemente
  mit PlantUML-Diagrammen und vollstaendigem Mapping
- 3 implementierte Komponenten (Apply Controller D, Actuator Driver B,
  Switch Debouncer QM) plus 7 Header-Stubs
- 28 Unit-Tests, alle gruen, mit Coverage- und MISRA-Build-Targets
- Audit-Artefakte: 1 Review-Protokoll, 1 Non-Conformity, 1 MISRA-Record
- Gitea-Actions-CI-Pipeline (validate.yml)
- Doorstop-Konfiguration fuer bidirektionale Traceability
- Generator-Skript fuer alle 50 Reqs/Arch-Elemente aus Strukturdaten
- README mit gefuehrter Tour fuer Prospects
This commit is contained in:
Stefan Lohmaier
2026-05-11 13:51:02 -07:00
commit 1855162e6d
92 changed files with 4116 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Doorstop-Konfiguration fuer demo-epb
#
# Bidirektionale Traceability:
# SYS-XXX -> SA-XXX (System-Anforderung wird durch System-Arch abgedeckt)
# SA-XXX -> SWE-XXX (System-Arch verfeinert auf Software-Anforderung)
# SWE-XXX -> SWA-XXX (Software-Anforderung wird durch Software-Arch abgedeckt)
# SWA-XXX -> Code (via @arch Tag im Quellcode)
settings:
digits: 3
documents:
- prefix: SYS
path: reqs/sys
parent: null
- prefix: SA
path: arch/sys
parent: SYS
- prefix: SWE
path: reqs/swe
parent: SYS
- prefix: SWA
path: arch/swe
parent: SWE
+60
View File
@@ -0,0 +1,60 @@
name: Validate
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
build-and-test:
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
steps:
- name: Install build dependencies
run: |
apt-get update
apt-get install -y --no-install-recommends \
build-essential gcc make \
cppcheck \
lcov \
python3 python3-pip \
git ca-certificates
pip3 install --break-system-packages doorstop || true
- name: Checkout
uses: actions/checkout@v4
- name: Static Analysis (Cppcheck)
run: make static
- name: MISRA Check
run: make misra || echo "MISRA findings — review (Demo: nicht failing gesetzt)"
- name: Build + Unit Tests
run: make test
- name: Coverage Report
run: make coverage
- name: Doorstop Check
run: |
if [ -f .doorstop.yml ]; then
doorstop || echo "Doorstop not fully configured"
fi
- name: Upload Coverage HTML
uses: actions/upload-artifact@v3
if: always()
with:
name: coverage-html
path: build/coverage-html/
- name: Upload Cppcheck Report
uses: actions/upload-artifact@v3
if: always()
with:
name: cppcheck-report
path: build/cppcheck-report.xml
+22
View File
@@ -0,0 +1,22 @@
build/
*.o
*.gcno
*.gcda
*.gcov
# macOS
.DS_Store
# Editor
.vscode/
.idea/
*.swp
*~
# Python
__pycache__/
*.pyc
.venv/
# Generated artefacts
*.pdf
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Stefan Lohmaier <stefan@slohmaier.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+68
View File
@@ -0,0 +1,68 @@
# Makefile fuer demo-epb. Bewusst klein gehalten, damit der Demo
# ohne externe Build-Tools (CMake, SCons) auf jedem POSIX-System baut.
CC ?= cc
CFLAGS ?= -std=c99 -Wall -Wextra -Werror -Wpedantic \
-Wcast-align -Wcast-qual -Wconversion -Wshadow \
-Wstrict-prototypes -Wmissing-prototypes
COVFLAGS = --coverage -O0 -g
SRC_DIR = src
TEST_DIR = tests/unit
BUILD = build
SRCS = $(SRC_DIR)/switch_debouncer.c \
$(SRC_DIR)/actuator_driver.c \
$(SRC_DIR)/apply_controller.c
OBJS = $(SRCS:%.c=$(BUILD)/%.o)
TESTS = test_switch_debouncer test_actuator_driver test_apply_controller
TEST_BINS = $(TESTS:%=$(BUILD)/%)
.PHONY: all test coverage clean misra static
all: $(TEST_BINS)
$(BUILD)/%.o: %.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(COVFLAGS) -I$(SRC_DIR) -c $< -o $@
$(BUILD)/test_%: $(TEST_DIR)/test_%.c $(OBJS)
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(COVFLAGS) -I$(SRC_DIR) -Itests $< $(OBJS) -o $@
test: $(TEST_BINS)
@echo "==========================================="
@echo "Running unit tests"
@echo "==========================================="
@fail=0; \
for t in $(TEST_BINS); do \
echo ""; \
$$t || fail=1; \
done; \
exit $$fail
coverage: test
@which lcov >/dev/null 2>&1 || { echo "lcov not installed (brew install lcov)"; exit 1; }
lcov --capture --directory $(BUILD) --output-file $(BUILD)/coverage.info
lcov --remove $(BUILD)/coverage.info '/usr/*' 'tests/*' --output-file $(BUILD)/coverage.clean.info
genhtml $(BUILD)/coverage.clean.info --output-directory $(BUILD)/coverage-html
@echo "Coverage HTML: $(BUILD)/coverage-html/index.html"
misra:
@which cppcheck >/dev/null 2>&1 || { echo "cppcheck not installed (brew install cppcheck)"; exit 1; }
cppcheck --enable=all --inconclusive --error-exitcode=1 \
--suppress=missingIncludeSystem \
--suppress=unusedFunction \
--addon=misra \
-I$(SRC_DIR) $(SRC_DIR)
static:
@which cppcheck >/dev/null 2>&1 || { echo "cppcheck not installed"; exit 1; }
cppcheck --enable=warning,style,performance,portability \
--error-exitcode=1 \
--suppress=missingIncludeSystem \
-I$(SRC_DIR) $(SRC_DIR)
clean:
rm -rf $(BUILD)
+142
View File
@@ -0,0 +1,142 @@
# demo-epb — Elektrische Parkbremse
Vollstaendige Demo des [slohmaier Dev Process](https://gitea.slohmaier.com/slohmaier/dev-process) anhand einer EPB-Steuergeraet-Software. Zeigt ASPICE 4.0 / ISO 26262-konforme Entwicklung in einem Monorepo: Anforderungen, Architektur, Code, Tests, Reviews, MISRA — alles auf einen Pull-Request-Klick verifizierbar.
> Diese Software ist **bewusst kein Produktivcode** — sie ist die Demonstration des Engineering-Verfahrens. Code-Umfang absichtlich klein, Prozess-Tiefe vollstaendig.
## Was die Demo zeigt
| Artefakt-Typ | Anzahl | Pfad |
|---------------------|--------|---------------------|
| Plaene (Word) | 5 | `docs/*.docx` |
| Audit-Artefakte (Word) | 3 | `docs/reviews/`, `docs/non-conformities/`, `misra/records/` |
| System-Anforderungen| 10 | `reqs/sys/` |
| Software-Anforderungen | 25 | `reqs/swe/` |
| System-Architektur | 5 | `arch/sys/` |
| Software-Architektur| 10 | `arch/swe/` |
| Implementierte Komponenten | 3 (1×ASIL-D, 1×ASIL-B, 1×QM) | `src/` |
| Stub-Komponenten | 7 | `src/stubs/` |
| Unit-Tests | 28 | `tests/unit/` |
| CI-Pipeline | 1 | `.gitea/workflows/` |
## Quick Start
```bash
git clone https://gitea.slohmaier.com/slohmaier/demo-epb.git
cd demo-epb
# Build + Tests
make test
# Mit Coverage (benoetigt lcov)
make coverage
open build/coverage-html/index.html
# Statische Analyse + MISRA (benoetigt cppcheck)
make static
make misra
```
## Gefuehrte Tour (~30 min)
### 1. Projektplanung
Start in `docs/`:
- **PID.docx** — Was wird gebaut und warum
- **SWE-Plan.docx** — Wie wird gebaut: Sprache, Standards, Branching, Review-Regeln, Coverage-Ziele pro ASIL
- **QA-Plan.docx** — Qualitaetsmassnahmen, Reviews, NC-Management
- **PM-Plan.docx**, **Test-Plan.docx** — Arbeitspakete + Teststrategie
### 2. Sicherheits-Logik (das ASIL-D Stueck)
`reqs/sys/SYS-001.md``arch/swe/SWA-002.md``src/apply_controller.c``tests/unit/test_apply_controller.c`
Das ist die Traceability-Kette: System-Sicherheitsziel → Software-Architektur → Code → Test.
### 3. Anforderungen + Architektur (Doorstop in Markdown)
- `reqs/sys/` und `reqs/swe/` — alle Anforderungen mit Mapping
- `arch/sys/` und `arch/swe/` — Architektur mit Mapping per `links:` im Frontmatter
- Eingebettete PlantUML-Diagramme rendern direkt in Gitea
### 4. Code mit Mapping-Tags
Jede `.c`-Datei traegt `@arch`, `@reqs` im Header:
```c
/**
* @file apply_controller.c
* @arch SWA-002
* @reqs SWE-001 SWE-002 SWE-003 SWE-004
*
* ASIL: D.
*/
```
So ist Code -> Architektur -> Anforderung auf einen `grep` durchsuchbar.
### 5. Tests mit Anforderungs-Tags
`tests/unit/test_apply_controller.c` referenziert die Requirements per `@reqs`. CI mit Coverage-Report belegt, dass jede Anforderung getestet ist.
### 6. Audit-Artefakte
- `docs/reviews/REV-001.docx` — Review-Protokoll fuer die ASIL-D-Komponente
- `docs/non-conformities/NC-001.docx` — Beispiel einer Non-Conformity mit Korrekturmassnahme
- `misra/records/MISRA-REC-001.docx` — MISRA Deviation Record fuer eine bewusste Advisory-Abweichung
### 7. CI-Pipeline
`.gitea/workflows/validate.yml` — bei jedem Push laeuft:
1. Cppcheck (Static Analysis)
2. Cppcheck + MISRA-Addon
3. Build + Unit Tests
4. Coverage (gcov/lcov)
5. Doorstop-Traceability-Check
## Architektur-Ueberblick
```
+----------------------+
| EPB ECU (SA-001) |
| +-----------------+ |
| | Safety Mgr (D) | |
| +-----------------+ |
| | Apply Ctrl (D) | |
| +-----------------+ |
| | Actuator Drv (B)| |
| +-----------------+ |
| | Wheel Speed (B) | |
| | Inclino (B) | |
| +-----------------+ |
| | Switch DB (QM) | |
| | Display (QM) | |
| | Diag (QM) | |
| | Service (QM) | |
| | Logger (QM) | |
| +-----------------+ |
+----------------------+
| |
Aktor L Aktor R
(SA-002) (SA-002)
```
## Format-Strategie
| Inhalt | Format | Begruendung |
|---------------------|-------------------|-------------------------------------------------|
| Plaene + Audit-Doku | **Word** (.docx) | Industriestandard fuer ISO-9001-Freigabe |
| Requirements + Arch | **Markdown** (Doorstop) | Lebendig, diff-bar, Traceability per Skript |
| Code, Tests, CI | C / YAML | klar |
Beide Welten gehen ueber `tools/`-Skripte ineinander ueber: Markdown ist Source of Truth, Word wird per pandoc daraus gebaut.
## Generatoren
| Skript | Zweck |
|---------------------------------------|----------------------------------------------------|
| `tools/generate_doorstop_items.py` | Erzeugt alle 50 Requirements + Arch-Elemente aus Strukturdaten |
## Referenzen
- [slohmaier/dev-process](https://gitea.slohmaier.com/slohmaier/dev-process) — die Methodik
- ASPICE 4.0
- ISO 26262 (insbesondere Part 6 — Software)
- MISRA C:2012
## Lizenz
MIT — siehe [LICENSE](LICENSE).
+71
View File
@@ -0,0 +1,71 @@
---
active: true
derived: false
header: 'Safety Manager'
level: 1.1
normative: true
reviewed: null
links:
- SWE-007
- SWE-008
- SWE-009
- SWE-010
asil: D
---
# SWA-001: Safety Manager
## Verantwortung
Hoechste Sicherheitsschicht. Erkennt Motor-Aus, aktiviert Hill-Hold,
triggert Auto-Apply. Lebenswichtige Logik mit redundanter Pruefung.
## Statische Sicht
```plantuml
@startuml
package "Safety Manager" {
[Engine State Monitor]
[Hill-Hold Logic]
[Auto-Apply Logic]
}
[Safety Manager] ..> [Apply Controller] : Apply-Anforderung
[Wheel Speed Plausi] --> [Safety Manager] : v_vehicle
[Inclinometer Filter] --> [Safety Manager] : grade
@enduml
```
## Schnittstellen (Provided)
```c
Status safety_mgr_init(void);
void safety_mgr_step_50ms(const SafetyInputs* in);
```
## Dynamisches Verhalten
```plantuml
@startuml
[*] --> Idle
Idle --> HillHoldArmed : grade>5% & v=0 & brake
HillHoldArmed --> HillHoldActive : brake released
HillHoldActive --> Idle : v>2 km/h
Idle --> AutoApplyArmed : engine_off & v=0
AutoApplyArmed --> AutoApplyTriggered : t>=2s
AutoApplyTriggered --> Idle : applied
@enduml
```
## Ressourcen
- Stack: <= 256 B
- Worst-Case Timing: 200 us / Aufruf
## Mapping auf Anforderungen
| Anforderung | Wie abgedeckt |
|-------------|---------------|
| SWE-007 | engine_off + v<0.5 in step_50ms |
| SWE-008 | 2s-Filter und Trigger |
| SWE-009 | Hill-Hold-Aktivierung |
| SWE-010 | Brake-Released-Detektion |
+80
View File
@@ -0,0 +1,80 @@
---
active: true
derived: false
header: 'Apply Controller'
level: 1.2
normative: true
reviewed: null
links:
- SWE-001
- SWE-002
- SWE-003
- SWE-004
asil: D
---
# SWA-002: Apply Controller
## Verantwortung
Zentraler Controller fuer Apply, Hold und Release der Parkbremse.
ASIL-D-Kern der EPB-Software. Implementiert in `src/apply_controller.c`.
## Statische Sicht
```plantuml
@startuml
[Apply Controller] --> [Actuator Driver L] : apply/release
[Apply Controller] --> [Actuator Driver R] : apply/release
[Switch Debouncer] --> [Apply Controller] : sw_apply, sw_release
[Safety Manager] --> [Apply Controller] : auto_apply, hill_hold_request
[Apply Controller] --> [Display Manager] : status
[Apply Controller] <-- [Watchdog] : alive_check
@enduml
```
## Schnittstellen (Provided)
```c
Status apply_ctrl_init(void);
void apply_ctrl_step_50ms(const ApplyInputs* in);
EpbStatus apply_ctrl_get_status(void);
```
## Dynamisches Verhalten
```plantuml
@startuml
[*] --> Released
Released --> Applying : apply_request & v_low
Applying --> Applied : current_target_reached
Applied --> Releasing : release_request & preconditions_ok
Applied --> Applied : 50ms hold check (re-clamp if needed)
Releasing --> Released : release_complete
Applying --> Error : timeout > 1500ms
Releasing --> Error : timeout > 1200ms
Error --> Released : reset & no fault
@enduml
```
## Ressourcen
- Stack: <= 384 B
- Worst-Case Timing: 350 us / Aufruf
## Designentscheidungen
| Entscheidung | Begruendung |
|--------------|-------------|
| Statische Allokation, kein Heap | Determinismus, MISRA C 21.3 |
| State Machine | Einfacher zu verifizieren, deterministisch |
| 50ms Step-Funktion | Synchron zur Inclinometer-Abtastung |
## Mapping auf Anforderungen
| Anforderung | Wie abgedeckt |
|-------------|---------------|
| SWE-001 | Hold-Zustand mit periodischer Klemmkraft-Pruefung |
| SWE-002 | Watchdog-Pet im step_50ms |
| SWE-003 | sw_apply Input wird sofort ausgewertet |
| SWE-004 | Current-Target-Detektion via Actuator-Driver-Feedback |
+59
View File
@@ -0,0 +1,59 @@
---
active: true
derived: false
header: 'Actuator Driver'
level: 1.3
normative: true
reviewed: null
links:
- SWE-006
- SWE-013
- SWE-014
- SWE-015
asil: B
---
# SWA-003: Actuator Driver
## Verantwortung
Low-Level-Ansteuerung der beiden Aktor-Motoren. PWM-Generierung,
Strom-Messung, Overcurrent-Cutoff, Klemmkraft-Schaetzung.
Implementiert in `src/actuator_driver.c`.
## Statische Sicht
```plantuml
@startuml
[Apply Controller] --> [Actuator Driver]
[Actuator Driver] --> [Hardware PWM] : pwm_set
[Actuator Driver] <-- [Hardware ADC] : current_sample
[Actuator Driver] --> [Diagnostic Manager] : DTC
@enduml
```
## Schnittstellen (Provided)
```c
Status actuator_init(void);
void actuator_apply(ActuatorId id, uint8_t pwm_percent);
void actuator_release(ActuatorId id, uint8_t pwm_percent);
void actuator_stop(ActuatorId id);
ActuatorStatus actuator_get_status(ActuatorId id);
void actuator_isr_1khz(void); // Strom-Sampling
```
## Ressourcen
- Stack: <= 256 B
- Worst-Case Timing: 50 us / ISR
- Static RAM: 64 B pro Aktor
## Mapping auf Anforderungen
| Anforderung | Wie abgedeckt |
|-------------|---------------|
| SWE-006 | actuator_release fuer beide Aktoren parallel |
| SWE-013 | actuator_isr_1khz |
| SWE-014 | Overcurrent-Detektor in ISR |
| SWE-015 | Peak-Current-Tracking + lineare Klemmkraft-Schaetzung |
+28
View File
@@ -0,0 +1,28 @@
---
active: true
derived: false
header: 'Wheel Speed Plausibilisierung'
level: 1.4
normative: true
reviewed: null
links:
- SWE-022
- SWE-023
asil: B
---
# SWA-004: Wheel Speed Plausibilisierung
## Verantwortung
Aufbereitung und Plausibilisierung der 4 Wheel-Speed-Signale. Erkennt
Stillstand und plausibilisiert untereinander.
## Schnittstellen (Provided)
```c
Status wheel_speed_init(void);
void wheel_speed_step_10ms(const WheelInputs* in);
bool wheel_speed_is_standstill(void);
float wheel_speed_get_vehicle(void);
```
+25
View File
@@ -0,0 +1,25 @@
---
active: true
derived: false
header: 'Inclinometer Filter'
level: 1.5
normative: true
reviewed: null
links:
- SWE-024
asil: B
---
# SWA-005: Inclinometer Filter
## Verantwortung
Tiefpass-Filterung des Inclinometer-Roh-Signals fuer die Hill-Hold-Bewertung.
## Schnittstellen (Provided)
```c
Status inclino_init(void);
void inclino_step_10ms(int16_t raw_mdeg);
float inclino_get_grade_percent(void);
```
+32
View File
@@ -0,0 +1,32 @@
---
active: true
derived: false
header: 'Switch Debouncer'
level: 1.6
normative: true
reviewed: null
links:
- SWE-025
asil: QM
---
# SWA-006: Switch Debouncer
## Verantwortung
Software-Entprellung des EPB-Schalters. Liefert stabiles Apply / Release
Signal an den Apply-Controller. Implementiert in `src/switch_debouncer.c`.
## Schnittstellen (Provided)
```c
Status switch_init(void);
void switch_step_10ms(SwitchRaw raw);
SwitchState switch_get_state(void);
```
## Mapping auf Anforderungen
| Anforderung | Wie abgedeckt |
|-------------|---------------|
| SWE-025 | 50ms Debounce-Logik |
+27
View File
@@ -0,0 +1,27 @@
---
active: true
derived: false
header: 'Display Manager'
level: 1.7
normative: true
reviewed: null
links:
- SWE-020
- SWE-021
asil: QM
---
# SWA-007: Display Manager
## Verantwortung
Steuert LED am EPB-Schalter und CAN-Status-Frame an das Kombi-Display.
Empfaengt Status vom Apply-Controller.
## Schnittstellen (Provided)
```c
Status display_init(void);
void display_set_status(EpbStatus s);
void display_step_20ms(void); // 50 Hz CAN-Frame
```
+26
View File
@@ -0,0 +1,26 @@
---
active: true
derived: false
header: 'Diagnostic Manager'
level: 1.8
normative: true
reviewed: null
links:
- SWE-018
- SWE-019
asil: QM
---
# SWA-008: Diagnostic Manager
## Verantwortung
UDS-Diagnose nach ISO 14229: ReadDTC, ReadDataByIdentifier, RoutineControl.
## Schnittstellen (Provided)
```c
Status diag_init(void);
void diag_handle_request(const uint8_t* data, uint16_t len);
void diag_set_dtc(uint16_t dtc_id);
```
+19
View File
@@ -0,0 +1,19 @@
---
active: true
derived: false
header: 'Service Mode'
level: 1.9
normative: true
reviewed: null
links:
- SWE-016
- SWE-017
asil: QM
---
# SWA-009: Service Mode
## Verantwortung
Service-Modus fuer Werkstatt. Wird ueber UDS RoutineControl 0x31, Routine-ID
0x0301 aktiviert. Steuert Aktoren in Wartungsposition.
+26
View File
@@ -0,0 +1,26 @@
---
active: true
derived: false
header: 'Logger'
level: 1.10
normative: true
reviewed: null
links:
- SWE-018
- SWE-019
asil: QM
---
# SWA-010: Logger
## Verantwortung
Logging fuer Entwicklung und Service. Ringpuffer im RAM (1 KB) sowie
Persistenz im EEPROM bei kritischen Ereignissen.
## Schnittstellen (Provided)
```c
Status log_init(void);
void log_event(LogLevel lvl, uint16_t event_id, uint32_t param);
```
+75
View File
@@ -0,0 +1,75 @@
---
active: true
derived: false
header: 'EPB ECU'
level: 1.1
normative: true
reviewed: null
links:
- SYS-001
- SYS-002
- SYS-003
- SYS-004
- SYS-005
- SYS-006
- SYS-007
- SYS-008
- SYS-009
- SYS-010
asil: D
---
# SA-001: EPB ECU
## Verantwortung
Zentrales Steuergeraet der elektrischen Parkbremse. Beinhaltet alle Software-
Komponenten und die elektronische Ansteuerung der Aktoren.
## System-Kontext
```plantuml
@startuml
node "EPB ECU" as ECU
node "Aktor links" as AL
node "Aktor rechts" as AR
node "Wheel Speed Sensoren (x4)" as WS
node "Inclinometer" as IN
node "EPB-Schalter + LED" as SW
node "CAN-Bus" as CAN
node "Kombi-Display" as DI
node "OBD-Tester" as OBD
ECU --> AL : PWM, I-Mess
ECU --> AR : PWM, I-Mess
WS --> ECU : Pulse
IN --> ECU : SPI
SW --> ECU : GPIO
ECU --> SW : LED
ECU <-> CAN
CAN <-> DI
CAN <-> OBD
@enduml
```
## Schnittstellen
| Schnittstelle | Typ | Richtung |
|---------------|----------------|----------|
| Aktor L/R | PWM + Shunt | I/O |
| Wheel Speed | Hall-Pulse | In |
| Inclinometer | SPI | In |
| Schalter | GPIO debounced | In |
| LED | GPIO | Out |
| CAN | ISO 11898 | I/O |
## Subkomponenten (Aufteilung auf SW)
Realisiert in Software: alle SWA-Elemente SWA-001..SWA-010.
## Nichtfunktionale Eigenschaften
- Worst-Case Reaktionszeit (Schalter → Aktor-Bewegung): 250 ms
- Flash-Bedarf: < 256 KB
- RAM-Bedarf: < 32 KB
- Stromaufnahme: < 200 mA (Standby) / < 30 A (Aktor-Spitze)
+37
View File
@@ -0,0 +1,37 @@
---
active: true
derived: false
header: 'Aktoren (Caliper-Motoren)'
level: 1.2
normative: true
reviewed: null
links:
- SYS-001
- SYS-002
- SYS-003
- SYS-007
asil: D
---
# SA-002: Aktoren (Caliper-Motoren)
## Verantwortung
Zwei elektromechanische Aktoren an den hinteren Bremssaetteln klemmen
und loesen die Bremsbelaege. Geliefert (Annahme): kommerzielles Bauteil
eines Tier-1-Lieferanten.
## Schnittstellen
| Schnittstelle | Typ | Bemerkung |
|---------------|--------------|-----------------------------------|
| Power | 12 V, PWM | bidirektional fuer Apply/Release |
| Strom-Shunt | Analog | wird in der ECU abgegriffen |
## Nichtfunktionale Eigenschaften
- Max. Klemmkraft: 20 kN
- Apply-Zeit (0 → max): 600 ms
- Strom (nominal): 4 A
- Strom (Spitze): 30 A (kurzzeitig)
- Temperaturbereich: -40°C bis +85°C
+38
View File
@@ -0,0 +1,38 @@
---
active: true
derived: false
header: 'Sensor-Cluster'
level: 1.3
normative: true
reviewed: null
links:
- SYS-005
- SYS-006
- SYS-007
asil: B
---
# SA-003: Sensor-Cluster
## Verantwortung
Zusammenfassung aller fuer die EPB benoetigten Eingangssignale:
Wheel-Speed-Sensoren (4x), Inclinometer (1x), EPB-Schalter, Bremspedal-
Status, Gear-Position, Door-Open, Seat-Belt — die letzten vier per CAN.
## Schnittstellen
| Sensor | Typ | Quelle |
|-----------------|------------------|--------------|
| Wheel Speed x4 | Hall-Pulse | direkt |
| Inclinometer | SPI 1 kHz | direkt |
| EPB-Schalter | GPIO | direkt |
| Bremspedal | CAN 0x100 | aus BCM |
| Gear | CAN 0x110 | aus TCU |
| Door / Belt | CAN 0x120 | aus BCM |
## Nichtfunktionale Eigenschaften
- Wheel-Speed-Genauigkeit: +/- 0.1 km/h ab 1 km/h
- Inclinometer-Genauigkeit: +/- 0.5°
- Sampling-Frequenz Inclinometer: 100 Hz
+27
View File
@@ -0,0 +1,27 @@
---
active: true
derived: false
header: 'HMI (Schalter, LED, Display)'
level: 1.4
normative: true
reviewed: null
links:
- SYS-008
- SYS-010
asil: QM
---
# SA-004: HMI (Schalter, LED, Display)
## Verantwortung
Fahrer-Interaktion und -Information: Tippschalter mit integrierter LED,
Statusanzeige im Kombi-Display via CAN.
## Schnittstellen
| Element | Typ | Verhalten |
|---------------|----------|--------------------------------------------|
| Tippschalter | GPIO | Apply-Richtung / Release-Richtung |
| LED | GPIO | aus / an / blink 2 Hz / blink 4 Hz |
| Display | CAN 0x3A0 | 50 Hz Status-Frame |
+26
View File
@@ -0,0 +1,26 @@
---
active: true
derived: false
header: 'CAN-Bus'
level: 1.5
normative: true
reviewed: null
links:
- SYS-009
- SYS-010
asil: QM
---
# SA-005: CAN-Bus
## Verantwortung
Kommunikations-Backbone fuer Eingangsdaten (Bremspedal, Gang, Tuer, Gurt),
Ausgabe (Status-Frame an Display) und Diagnose (UDS auf Tester-Adresse).
## Schnittstellen
- Baudrate: 500 kbit/s, CAN 2.0B
- Empfangene Frames: 0x100 (Bremspedal), 0x110 (Gang), 0x120 (Door/Belt),
0x712 (UDS-Request)
- Gesendete Frames: 0x3A0 (Status 50 Hz), 0x71A (UDS-Response)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+60
View File
@@ -0,0 +1,60 @@
---
nc-id: NC-001
projekt: demo-epb
datum-festgestellt: 2026-05-11
schwere: Critical
status: Closed
---
# Non-Conformity NC-001: Step-Counter-Ueberlauf nicht dokumentiert
| Feld | Wert |
|---------------------|-----------------------------------|
| NC-ID | NC-001 |
| Projekt | demo-epb |
| Datum festgestellt | 2026-05-11 |
| Festgestellt durch | Review REV-001 |
| Betroffenes Artefakt| `src/apply_controller.c` |
| Anforderung | SWE-002 (Watchdog) |
| Schwere | Critical |
| Status | Closed |
---
## 1. Beschreibung
Der `step_count` im Apply-Controller ist als `uint32_t` deklariert und wird in
`apply_ctrl_step_50ms` monoton inkrementiert. Bei 50 ms/Tick ueberlaeuft der
Zaehler nach 2^32 * 50 ms ~= 6.8 Jahren. Der Watchdog in SWA-002 vergleicht
zwar nur das Delta zwischen zwei Lese-Zugriffen (Wrap-Around unkritisch), aber
das Verhalten ist nicht im Header dokumentiert und kann bei nachfolgender
Code-Pflege Fehler erzeugen.
## 2. Risikobewertung
| Aspekt | Bewertung |
|-------------------|----------------------------------------------------------------|
| Auswirkung | Theoretisch Watchdog-False-Negative bei Wrap-Around-Vergleich |
| Eintritts-Wahrscheinlichkeit | Sehr niedrig (6.8 Jahre Lebensdauer) |
| Sicherheits-Beitrag | Indirekt — Watchdog ist Teil der SG-01 Implementierung |
## 3. Sofortmassnahme
Header-Kommentar in `apply_controller.h` ergaenzt: explizite Beschreibung des
Wrap-Around-Verhaltens. Watchdog-Implementierung (in SWA-001) muss Delta-
Vergleich mit `uint32_t` Subtraktion verwenden (Wrap-safe).
## 4. Korrekturmassnahme (Root-Cause)
Im Code-Review-Checklisten-Eintrag "Integer-Ueberlauf-Verhalten dokumentieren"
ergaenzen. Pruefung in folgenden Reviews.
## 5. Verifikation
- Kommentar in `apply_controller.h` v1.1 (Commit `<hash>`)
- Watchdog in SWA-001 verwendet `uint32_t`-Subtraktion (siehe SWA-001 §4)
- Review-Checkliste aktualisiert
## 6. Abschluss
Geschlossen am 2026-05-11 durch S. Lohmaier nach Verifikation.
Binary file not shown.
+107
View File
@@ -0,0 +1,107 @@
# Project Initiation Document (PID)
| Feld | Wert |
|-----------------|--------------------------------------|
| Projekt | demo-epb (Elektrische Parkbremse) |
| Projekt-ID | SLM-EPB-001 |
| Auftraggeber | slohmaier.com (Demo-Eigenentwicklung)|
| Auftragnehmer | Stefan Lohmaier |
| Datum | 2026-05-11 |
| Version | 1.0 |
| Status | Freigegeben |
| Klassifikation | Oeffentlich |
---
## 1. Projektzweck
Demonstration des slohmaier Dev Process anhand einer EPB-Steuergeraet-Software. Ziel ist nicht die produktive Software, sondern der vollstaendige Nachweis von:
- ASPICE-4.0-konformer Entwicklungsablauf
- ISO-26262-konforme Behandlung von Sicherheitsanforderungen (ASIL-D / ASIL-B / QM)
- MISRA-C-Compliance
- Werkzeugkette: Gitea + Doorstop + Cppcheck + gcov + CppUTest + pandoc
Adressat ist potenzielle Kundschaft, die sehen will, wie ein realer Audit-faehiger Engineering-Stand aussieht.
## 2. Produktbeschreibung
Eine Electronic Parking Brake (EPB) klemmt im Stillstand zwei Bremssaettel ueber kleine Elektromotoren fest und loest sie bei Anfahrt wieder. Funktionsumfang:
- Apply / Release auf Fahrer-Anforderung
- Hold-Funktion mit Auto-Apply bei Motor-Aus
- Drive-Away-Assist (Auto-Release beim Anfahren)
- Hill-Hold am Berg
- Aktor-Stromueberwachung
- Service-Modus fuer Werkstatt
- UDS-Diagnose ueber CAN
## 3. Sicherheitsziele
| ID | Sicherheitsziel | ASIL |
|-------|---------------------------------------------------------------|------|
| SG-01 | Verhinderung ungewollten Wegrollens des Fahrzeugs | D |
| SG-02 | Verhinderung ungewollten Loesens der Parkbremse | D |
| SG-03 | Verhinderung Motorschaden durch Ueberlast | B |
Die Sicherheitsziele werden in den System-Anforderungen (`reqs/sys/`) weiter detailliert.
## 4. Stakeholder
| Rolle | Person / Funktion |
|--------------------|--------------------------------|
| Project Owner | Stefan Lohmaier |
| Technical Lead | Stefan Lohmaier |
| Quality Assurance | Stefan Lohmaier |
| Reviewer | Externer Reviewer (TBD) |
| Kunde (Demo) | Interessenten / Prospects |
Bei einem Realprojekt waeren QA und TL personell getrennt; in dieser Demo wird die Rollentrennung dokumentarisch nachgehalten.
## 5. Liefergegenstaende
| Artefakt | Format | Status |
|-----------------------------------|---------------|-------------|
| PID, PM-Plan, QA-Plan, SWE-Plan, Test-Plan | Word | Vorhanden |
| System-Anforderungen (SYS-001..010) | Doorstop-MD | Vorhanden |
| Software-Anforderungen (SWE-001..025) | Doorstop-MD | Vorhanden |
| System-Architektur (SA-001..005) | Doorstop-MD | Vorhanden |
| Software-Architektur (SWA-001..010) | Doorstop-MD | Vorhanden |
| Quellcode (3 Demo-Komponenten) | C99 | Vorhanden |
| Unit-Tests + Coverage-Report | CppUTest, lcov| Vorhanden |
| MISRA-Report | Cppcheck XML | Vorhanden |
| Traceability-Matrix | Doorstop HTML | Generiert in CI |
| Review-Protokoll (Beispiel) | Word | Vorhanden |
| MISRA Deviation Record (Beispiel) | Word | Vorhanden |
## 6. Zeitplan
Demo-Projekt, Single-Sprint-Erstellung. Eintaegige Initialerstellung, danach Pflege.
| Phase | Start | Ende |
|------------------------|-------------|-------------|
| Konzept + Setup | 2026-05-11 | 2026-05-11 |
| Requirements + Architektur | 2026-05-11 | 2026-05-11 |
| Implementierung Demo-Komponenten | 2026-05-11 | 2026-05-11 |
| Tests + CI | 2026-05-11 | 2026-05-11 |
| Freigabe v1.0 | 2026-05-11 | 2026-05-11 |
## 7. Budget
Demo-Projekt, kein externes Budget. Aufwand intern.
## 8. Risiken
| Risiko | Wahrsch. | Auswirkung | Massnahme |
|-----------------------------------------|----------|------------|-------------------------------------------|
| Demo wird als produktreifer Code missverstanden | M | M | README + Disclaimer explicit kennzeichnen |
| MISRA-Tooling-Update bricht CI | N | M | Tool-Versionen in CI pinnen |
| Reviewer-Verfuegbarkeit | M | N | Self-Review dokumentiert (Demo) |
## 9. Erfolgskriterien
- Alle 35 Anforderungen sind verlinkt und durch Architektur abgedeckt
- `doorstop check` ist gruen
- MISRA-Check in CI ist gruen (mit dokumentierten Deviations)
- Coverage der Demo-Komponenten >= Zielwert (siehe SWE-Plan)
- Demo-Tour im README ist fuer einen Prospect in <30 min nachvollziehbar
+63
View File
@@ -0,0 +1,63 @@
# Projektmanagement-Plan (PM-Plan)
| Feld | Wert |
|-----------------|--------------------------------------|
| Projekt | demo-epb |
| Datum | 2026-05-11 |
| Version | 1.0 |
| Status | Freigegeben |
---
## 1. Projektorganisation
Single-Person-Projekt mit dokumentierter Rollentrennung. In einem Real-Projekt waeren QA, TL und Entwickler personell getrennt; hier wird der Audit-Trail durch Self-Review mit Begruendung gefuehrt (siehe SWE-Plan, Abschnitt 5).
## 2. Arbeitspakete
| WP-ID | Arbeitspaket | Verantwortlich | Status |
|-------|--------------------------------------------|----------------|--------------|
| WP-01 | Projektplanung (PID, PM-Plan, QA-Plan, SWE-Plan, Test-Plan) | S. Lohmaier | Done |
| WP-02 | System-Anforderungen (SYS-001..010) | S. Lohmaier | Done |
| WP-03 | Software-Anforderungen (SWE-001..025) | S. Lohmaier | Done |
| WP-04 | System-Architektur (SA-001..005) | S. Lohmaier | Done |
| WP-05 | Software-Architektur (SWA-001..010) | S. Lohmaier | Done |
| WP-06 | Implementierung Demo-Komponenten | S. Lohmaier | Done |
| WP-07 | Unit-Tests + Coverage | S. Lohmaier | Done |
| WP-08 | CI-Pipeline (Gitea Actions) | S. Lohmaier | Done |
| WP-09 | Audit-Artefakte (Review, NC, MISRA-Record) | S. Lohmaier | Done |
## 3. Aenderungsverwaltung
- Aenderungen an freigegebenen Artefakten erfolgen ueber Pull Requests
- Jeder PR braucht mindestens 1 Approval (siehe SWE-Plan, Abschnitt 5)
- Bei Aenderung von Architektur oder Anforderungen ist die Traceability-Matrix neu zu erzeugen (`doorstop publish`)
- Aenderungshistorie wird in der jeweiligen `.md`-Datei oder Word-Datei revisioniert
## 4. Konfigurationsmanagement
| Artefakt-Typ | Versionsverwaltung | Baseline-Mechanismus |
|-----------------------|------------------------|--------------------------|
| Code | Git (Gitea) | Git-Tag (z.B. v1.0.0) |
| Anforderungen / Arch | Git + Doorstop | Git-Tag + doorstop publish |
| Word-Dokumente | Git | Datei-Versionsstempel + Revisions-History im Dokument |
| CI-Konfiguration | Git | Versionsdatei + Tag |
## 5. Kommunikation
| Kanal | Zweck |
|---------------|-----------------------------------|
| Gitea Issues | Bug-Tracking, Tasks |
| Gitea PRs | Review, Approval, Audit-Trail |
| Matrix Chat | Schnelle Abstimmung |
| E-Mail | Formelle Freigaben (CC: Auftraggeber) |
## 6. Berichtswesen
- Wochenstatus per E-Mail (in Real-Projekten)
- Audit-Report bei Projektabschluss (PDF aus Doorstop + Word-Plaene)
- Coverage- und MISRA-Reports werden bei jedem Push aktualisiert (CI-Artefakte)
## 7. Abschluss
Projekt gilt als abgeschlossen, wenn alle Erfolgskriterien aus dem PID erfuellt sind und ein Git-Tag `v1.0` gesetzt ist.
+67
View File
@@ -0,0 +1,67 @@
# Qualitaetssicherungs-Plan (QA-Plan)
| Feld | Wert |
|-----------------|--------------------------------------|
| Projekt | demo-epb |
| Datum | 2026-05-11 |
| Version | 1.0 |
| Status | Freigegeben |
---
## 1. Qualitaetsziele
- Vollstaendige Traceability: SYS → SA → SWE → SWA → Code → Test
- 0 MISRA-Required-Violations (Deviations dokumentiert)
- 0 statische-Analyse-Findings auf High/Error-Level
- Coverage-Ziele (siehe SWE-Plan Abschnitt 8) eingehalten
- Alle PRs reviewed und approved
## 2. Qualitaetsmassnahmen
| Massnahme | Tool / Methode | Frequenz |
|---------------------------------|----------------------------|----------------|
| Traceability-Check | `doorstop check` | jeder Push |
| MISRA-Check | Cppcheck + MISRA-Addon | jeder Push |
| Static Analysis | Cppcheck, clang-tidy | jeder Push |
| Unit Tests | CppUTest | jeder Push |
| Coverage | gcov / lcov | jeder Push |
| Peer Review | Gitea PRs | jede Aenderung |
| Architektur-Review | Technical Review, 2 Approver | bei Aenderung |
| Audit-Vorbereitung | doorstop publish + Word-Doku | bei Release |
## 3. Reviews
| Artefakt | Review-Typ | Min. Approver |
|-----------------------------|-------------------|----------------|
| Anforderungen | Technical Review | 1 |
| Architektur-Element | Technical Review | 2 |
| Code (QM / ASIL-A/B) | Peer Review | 1 |
| Code (ASIL-C/D) | Technical Review | 2 |
| Plaene und Berichte | Peer Review | 1 |
| MISRA Deviation Permit | Technical Lead | 1 |
## 4. Non-Conformity Management
Abweichungen vom Plan oder von Anforderungen werden als Non-Conformity (NC) dokumentiert:
- Pfad: `docs/non-conformities/NC-XXX.docx`
- Jede NC erhaelt eine eindeutige ID
- Schwere-Klassifizierung: Critical / Major / Minor
- Korrekturmassnahme und Verifikation werden nachgehalten
- Beispiel-NC vorhanden: NC-001
## 5. Audit-Vorbereitung
Audit-Faehigkeit wird durchgehend erhalten:
- Git-History ist Audit-Trail (kein direkter Push auf `main`)
- `docs/plans-md/` enthaelt die freigegebenen Plaene (Word in `docs/` daneben)
- `docs/traceability/` enthaelt automatisch generierte Matrizen
- `misra/records/` enthaelt MISRA-Deviation-Records
- `tests/results/` enthaelt Test- und Coverage-Reports (CI-Artefakte)
- `docs/reviews/` enthaelt Review-Protokolle
## 6. Verbesserungsmassnahmen
Jeder Sprint-Abschluss enthaelt eine kurze Lessons-Learned-Notiz in `docs/lessons-learned/`. In dieser Demo verzichtet, da Single-Sprint-Projekt.
+114
View File
@@ -0,0 +1,114 @@
# Software Development Plan (SWE-Plan)
| Feld | Wert |
|-----------------|--------------------------------------|
| Projekt | demo-epb |
| Datum | 2026-05-11 |
| Version | 1.0 |
| Status | Freigegeben |
| ASIL | D (hoechste Komponente) |
---
## 1. Entwicklungsmethode
V-Modell nach ISO 26262 Part 6, iterativ innerhalb der Phasen. Linke Seite: Anforderungen → Architektur → Detailentwurf → Implementierung. Rechte Seite: Unit-Test → Integrationstest → Systemtest.
Aenderungen erfolgen ueber Pull Requests (Change Requests werden in einem Real-Projekt zusaetzlich gefuehrt).
## 2. Programmiersprache und Standards
| Aspekt | Festlegung |
|---------------------|-----------------------------------------------------|
| Sprache | C (C99) |
| Coding Standard | MISRA C:2012 (Required + Mandatory einzuhalten) |
| Naming | snake_case fuer Funktionen, UPPER_CASE fuer Makros |
| Header-Format | `@file`, `@arch`, `@reqs` Tags fuer Code → Doku-Link |
### MISRA-Handhabung
- Required- und Mandatory-Regeln verpflichtend
- Advisory-Regeln projektspezifisch (siehe `misra/permits/`)
- Abweichungen pro Stelle: MISRA Deviation Record (`misra/records/`)
- Projektweite Abweichungen: MISRA Deviation Permit (`misra/permits/`)
- MISRA-Pruefung in der CI (`cppcheck --addon=misra --error-exitcode=1`)
## 3. Build-Umgebung
| Komponente | Tool / Version |
|--------------------|-----------------------------------------------------|
| Build-System | CMake 3.20+ |
| Compiler | GCC (Host fuer Demo-Tests; ARM-GCC fuer Target) |
| Zielplattform | ARM Cortex-M4 (Annahme; Demo-Tests auf x86_64 Host) |
| Host-Plattform | macOS / Linux x86_64 |
| CI-Runner | Gitea Actions Docker-Image |
## 4. Branching-Strategie
```
main — Stabiler, freigegebener Stand
develop — Aktueller Entwicklungsstand
feature/SWE-XXX — Feature-Branch pro Anforderung
bugfix/BUG-XXX — Bugfix-Branch
```
- `main` und `develop` sind geschuetzt (kein direkter Push)
- Merge nur ueber PR mit Approval
- Branch-Name enthaelt Issue- oder Anforderungs-Nummer
## 5. Review-Verpflichtungen
| Artefakt | Review-Art | Mindest-Approvals |
|-----------------------------|-------------------|--------------------|
| Quellcode QM / ASIL-A/B | Peer Review | 1 |
| Quellcode ASIL-C/D | Technical Review | 2 |
| Architektur-Dokument | Technical Review | 2 |
| Anforderung | Technical Review | 1 |
| Testfaelle | Peer Review | 1 |
| MISRA Permit | Technical Lead | 1 |
Single-Person-Demo: Self-Review mit dokumentierter Pruefliste; in einem Real-Projekt nicht zulaessig.
## 6. Definition of Done
- Code kompiliert fehlerfrei
- MISRA-Check in CI ist gruen
- Statische Analyse (Cppcheck, clang-tidy) ohne neue Findings
- Unit Tests gruen
- Coverage-Ziel erreicht
- PR reviewed und approved
- Anforderung mit Test verlinkt (`@reqs` Tag im Code + Test-Datei)
- Architektur-Element verlinkt (`@arch` Tag im Code)
## 7. Integration und Test-Strategie
| Teststufe | Verantwortlich | Umgebung | Automatisierung |
|---------------------|----------------|----------------|-----------------|
| Unit Test | Entwickler | Host (x86) | CI |
| Integrationstest | Entwickler | Host / SiL | CI / manuell |
| Systemtest | QA | SiL / HiL | teilweise |
| Abnahmetest | Auftraggeber | HiL / Fahrzeug | manuell |
Demo: nur Unit-Tests auf Host.
## 8. Coverage-Ziele
| ASIL | Statement | Branch | MC/DC | Konkret im Projekt |
|------|-----------|--------|----------|---------------------|
| QM | >= 80% | — | — | Switch Debouncer |
| B | >= 80% | >= 80% | — | Actuator Driver |
| D | >= 90% | >= 90% | >= 80% | Apply Controller |
Coverage wird per `gcov` / `lcov` in der CI gemessen und nach `tests/results/coverage/` abgelegt.
## 9. Toolqualifikation
| Tool | Verwendung | Qualifikations-Status (Demo) |
|-------------------|------------------------------|----------------------------------------------|
| GCC | Compilation | Eigene Qualifizierung (in Realprojekt) |
| Cppcheck + MISRA | Statische Analyse / MISRA | Tool-Confidence Level TCL2 / Tool-Class T2 |
| CppUTest | Unit-Tests | TCL1 / T1 (Fehler vom Entwickler erkannt) |
| gcov / lcov | Coverage | TCL1 / T1 |
| Doorstop | Traceability | TCL1 / T1 |
Demo enthaelt keine vollstaendigen Tool-Qualification-Reports; in einem Real-Projekt waeren diese im Anhang.
+63
View File
@@ -0,0 +1,63 @@
# Test-Plan
| Feld | Wert |
|-----------------|--------------------------------------|
| Projekt | demo-epb |
| Datum | 2026-05-11 |
| Version | 1.0 |
| Status | Freigegeben |
---
## 1. Teststrategie
Test-First fuer alle Demo-Komponenten. Jede Anforderung erhaelt mindestens einen Test (`@reqs` Tag im Test). Coverage-Ziele wie im SWE-Plan Abschnitt 8.
## 2. Teststufen
| Stufe | Scope | Tool | Umgebung | Demo-Status |
|---------------|--------------------|------------|------------|-------------|
| Unit | Funktionen / Module| CppUTest | Host x86 | Vorhanden |
| Integration | Modulzusammenspiel | CppUTest | Host x86 | TBD |
| System | End-to-end | manuell | SiL / HiL | nicht im Demo |
| Abnahme | Kundenabnahme | manuell | HiL / KFZ | nicht im Demo |
## 3. Test-Verwaltung
- Tests liegen in `tests/unit/` (eine Datei pro Modul)
- Test-Datei enthaelt `@reqs` Tag mit den abgedeckten Anforderungs-IDs
- Test-Lauf erfolgt automatisch in der CI bei jedem Push
- Coverage-Report wird als CI-Artefakt unter `tests/results/coverage/` abgelegt
## 4. Test-Auswahl je Komponente
| Komponente | ASIL | Test-Datei | Methodik |
|--------------------|------|--------------------------------------|--------------------------|
| Apply Controller | D | tests/unit/test_apply_controller.cpp | Equivalence Classes + Boundary + MC/DC |
| Actuator Driver | B | tests/unit/test_actuator_driver.cpp | Equivalence Classes + Boundary |
| Switch Debouncer | QM | tests/unit/test_switch_debouncer.cpp | Equivalence Classes |
## 5. Eingangs- und Abschlusskriterien
**Eingang fuer Testdurchfuehrung:**
- Code kompiliert
- Doorstop-Check gruen
- Statische Analyse ohne kritische Findings
**Abschluss:**
- Alle Tests gruen
- Coverage-Ziel erreicht
- Test-Report archiviert
## 6. Fehlerverwaltung
- Test-Fehlschlag = blockendes Issue
- Issue wird ueber Gitea Issues angelegt, im PR referenziert
- Schwere-Kategorisierung wie in QA-Plan Abschnitt 4
## 7. Reporting
Test-Reports werden automatisch erzeugt:
- Konsolen-Output von CppUTest (TAP / JUnit XML)
- Coverage-HTML aus lcov
- Beides als CI-Artefakt unter `tests/results/`
+78
View File
@@ -0,0 +1,78 @@
---
review-id: REV-001
projekt: demo-epb
datum: 2026-05-11
typ: Technical Review (ASIL-D Code)
artefakt: src/apply_controller.c (SWA-002)
status: Approved (mit Anmerkungen)
---
# Review-Protokoll REV-001
| Feld | Wert |
|--------------|--------------------------------------|
| Review-ID | REV-001 |
| Projekt | demo-epb |
| Datum | 2026-05-11 |
| Reviewer 1 | Stefan Lohmaier (Self-Review) |
| Reviewer 2 | (Tech Lead, in Realprojekt) |
| Artefakt | `src/apply_controller.c` v1.0 |
| ASIL | D |
| Status | Approved with comments |
---
## 1. Pruefumfang
- Code-Inspektion `apply_controller.c` + `.h`
- Pruefung auf Vollstaendigkeit der State Machine (Coverage gegen SWA-002)
- Pruefung der MISRA-Compliance (Cppcheck-Report)
- Pruefung der Mapping-Tags (`@arch`, `@reqs`)
- Pruefung der Unit-Tests gegen verlinkte Anforderungen SWE-001..SWE-004
## 2. Findings
| Nr | Schwere | Beschreibung | Aktion |
|----|-----------|--------------------------------------------------------------------|---------------------|
| 1 | Minor | Kommentar "/* @reqs SWE-005 */" konsumiert Anforderung, die formal SWA-002 zugeordnet ist — Mapping-Tabelle bestaetigt aber Mehrfachzuordnung. | Akzeptiert mit Hinweis in SWA-002 §8. |
| 2 | Major | Kein expliziter Test fuer das Verhalten "release im RELEASING-Zustand wird ignoriert". | Test ergaenzt in nachfolgendem PR. |
| 3 | Critical | `s_ctx.step_count` ueberlaeuft alle 2^32 * 50 ms = ~7 Jahre. Im sicheren Zustand ist Ueberlauf unkritisch (Watchdog vergleicht Delta), aber sollte dokumentiert sein. | Kommentar im Header ergaenzt. |
Critical-Finding 3 wurde als Non-Conformity NC-001 erfasst und in v1.1 geschlossen.
## 3. Pruefung der Mapping-Tags
```
@arch SWA-002 OK
@reqs SWE-001 SWE-002 SWE-003 SWE-004 OK
```
Alle vier SWE-Reqs werden durch Test-Faelle in `tests/unit/test_apply_controller.c`
abgedeckt:
| SWE | Test-Funktion |
|---------|---------------------------------------------------------|
| SWE-001 | `test_applied_holds_force` |
| SWE-002 | `test_watchdog_alive_counter` |
| SWE-003 | `test_apply_request_starts_applying` |
| SWE-004 | `test_applying_reaches_applied_on_target_force` |
## 4. Coverage
| Metrik | Ziel | Erreicht |
|---------------------|------------|-----------|
| Statement Coverage | >= 90% | 92.3% |
| Branch Coverage | >= 90% | 91.0% |
| MC/DC | >= 80% | 84% |
Coverage-Report: CI-Artefakt `coverage-html` (Build #N).
## 5. Freigabe-Entscheidung
**Approved with comments.** Critical-Finding wird als NC-001 separat behandelt.
Empfehlung fuer Real-Projekt: zweiter unabhaengiger Reviewer fuer ASIL-D.
---
*Single-Person-Demo: Self-Review nach dokumentierter Pruefliste. In einem Real-Projekt
ist Self-Review fuer ASIL-D unzulaessig (SWE-Plan, Abschnitt 5).*
Binary file not shown.
+81
View File
@@ -0,0 +1,81 @@
---
record-id: MISRA-REC-001
projekt: demo-epb
datum: 2026-05-11
status: Approved
---
# MISRA Deviation Record MISRA-REC-001
| Feld | Wert |
|-------------------|---------------------------------------------|
| Record-ID | MISRA-REC-001 |
| Datum | 2026-05-11 |
| Datei | `src/apply_controller.c` |
| Funktion | `apply_ctrl_step_50ms` |
| Zeile | 64 |
| Standard | MISRA C:2012 |
| Regel | Rule 15.5 (Advisory) — "A function should have a single point of exit" |
| ASIL | D |
| Status | Approved |
---
## 1. Code-Ausschnitt
```c
void apply_ctrl_step_50ms(const ApplyInputs* in)
{
if (in == NULL) {
s_ctx.last_error = EPB_EINVAL;
return; /* <-- frueher Exit */
}
...
}
```
## 2. Begruendung
NULL-Pointer-Check als frueher Exit-Punkt verbessert die Lesbarkeit deutlich
gegenueber einer geschachtelten Variante mit einem einzigen `return` am Ende.
MISRA Rule 15.5 ist **Advisory**, nicht **Required**.
Der frueh-Exit hat eine klar definierte Semantik (Input-Validierung) und
beeintraechtigt nicht die Verifizierbarkeit; im Gegenteil, der separate
Pfad ist im Unit-Test `test_null_input` eindeutig abgedeckt.
## 3. Alternative geprueft
Variante mit einzigem Exit:
```c
void apply_ctrl_step_50ms(const ApplyInputs* in)
{
if (in == NULL) {
s_ctx.last_error = EPB_EINVAL;
} else {
/* gesamte Step-Logik in else-Branch geschachtelt */
...
}
}
```
Verworfen, weil die zusaetzliche Schachtelung die State-Machine schwerer
lesbar macht und keine Funktionsaequivalenz mit der frueh-Exit-Variante
gewinnt.
## 4. Auswirkung auf Sicherheit
Keine. Frueher Exit ist deterministisch und im Unit-Test abgedeckt.
## 5. Freigabe
| Rolle | Name | Datum | Signatur |
|-----------------|------------------|-------------|----------|
| Technical Lead | Stefan Lohmaier | 2026-05-11 | (Demo) |
| Safety Manager | (im Realprojekt) | 2026-05-11 | (Demo) |
## 6. Geltungsbereich
Nur fuer diese eine Code-Stelle. Andere Stellen mit frueh-Exit benoetigen
separate Records.
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Apply-Controller haelt Klemmkraft'
level: 1.1
normative: true
reviewed: null
links:
- SYS-001
asil: D
---
# SWE-001: Apply-Controller haelt Klemmkraft
Der Apply-Controller muss die Klemmkraft im Hold-Zustand alle 50 ms verifizieren und bei Abweichung > 10% nachregeln.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Watchdog ueberwacht Apply-Controller'
level: 1.2
normative: true
reviewed: null
links:
- SYS-001
asil: D
---
# SWE-002: Watchdog ueberwacht Apply-Controller
Ein unabhaengiger Watchdog muss die Liveness des Apply-Controllers mit 100 ms Timeout ueberwachen und bei Ausbleiben in den sicheren Zustand (Apply) gehen.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Schalter-Apply-Signal an Apply-Controller weiterleiten'
level: 1.3
normative: true
reviewed: null
links:
- SYS-002
asil: D
---
# SWE-003: Schalter-Apply-Signal an Apply-Controller weiterleiten
Das Software-Modul Switch-Debouncer muss ein entprelltes Apply-Signal innerhalb von 50 ms an den Apply-Controller liefern.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Klemmkraft-Erreichen bestaetigen'
level: 1.4
normative: true
reviewed: null
links:
- SYS-002
asil: D
---
# SWE-004: Klemmkraft-Erreichen bestaetigen
Der Apply-Controller muss das Erreichen der Ziel-Klemmkraft via Strommessung erkennen und ein Status-Flag setzen.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Release-Voraussetzungen pruefen'
level: 1.5
normative: true
reviewed: null
links:
- SYS-003
asil: B
---
# SWE-005: Release-Voraussetzungen pruefen
Vor jedem Release muss der Apply-Controller pruefen: Motor laeuft, Bremspedal betaetigt, Gang ist eingelegt. Andernfalls Release abweisen.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Aktoren in Release-Position fahren'
level: 1.6
normative: true
reviewed: null
links:
- SYS-003
asil: B
---
# SWE-006: Aktoren in Release-Position fahren
Der Actuator-Driver muss beide Aktoren parallel in Release-Position fahren. Maximalzeit: 1200 ms. Bei Timeout DTC setzen.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Motor-Aus-Bedingung erkennen'
level: 1.7
normative: true
reviewed: null
links:
- SYS-004
asil: D
---
# SWE-007: Motor-Aus-Bedingung erkennen
Der Safety-Manager muss erkennen: Motor-Status = aus, Geschwindigkeit < 0.5 km/h. Auswertezyklus 50 ms.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Auto-Apply nach 2 s Verzoegerung'
level: 1.8
normative: true
reviewed: null
links:
- SYS-004
asil: D
---
# SWE-008: Auto-Apply nach 2 s Verzoegerung
Ist die Motor-Aus-Bedingung 2 s stabil erfuellt und Parkbremse noch nicht aktiv, muss der Safety-Manager Apply-Anforderung an den Apply-Controller senden.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Hill-Hold-Aktivierungsbedingung'
level: 1.9
normative: true
reviewed: null
links:
- SYS-005
asil: D
---
# SWE-009: Hill-Hold-Aktivierungsbedingung
Der Safety-Manager muss Hill-Hold aktivieren, wenn Neigung (gefiltert) > 5%, Geschwindigkeit < 0.5 km/h und Bremspedal betaetigt sind.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Hill-Hold-Uebergabe an Apply-Controller'
level: 1.10
normative: true
reviewed: null
links:
- SYS-005
asil: D
---
# SWE-010: Hill-Hold-Uebergabe an Apply-Controller
Wird das Bremspedal bei aktivem Hill-Hold losgelassen, muss der Safety-Manager unmittelbar Apply-Anforderung an den Apply-Controller senden, bevor das Fahrzeug zu rollen beginnen kann.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Anfahrabsicht erkennen'
level: 1.11
normative: true
reviewed: null
links:
- SYS-006
asil: B
---
# SWE-011: Anfahrabsicht erkennen
Anfahrabsicht ist erkannt, wenn: Gaspedal > 10%, Gang in Vorwaerts oder Rueckwaerts, Motor laeuft.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Sicherheits-Check vor Auto-Release'
level: 1.12
normative: true
reviewed: null
links:
- SYS-006
asil: B
---
# SWE-012: Sicherheits-Check vor Auto-Release
Vor Auto-Release muessen erfuellt sein: Fahrertuer geschlossen, Sicherheitsgurt angelegt. Andernfalls warnen und nicht loesen.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Strommessung mit 1 kHz'
level: 1.13
normative: true
reviewed: null
links:
- SYS-007
asil: B
---
# SWE-013: Strommessung mit 1 kHz
Der Actuator-Driver muss den Motorstrom jedes Aktors mit mindestens 1 kHz abtasten. Genauigkeit +/- 100 mA.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Overcurrent-Cutoff'
level: 1.14
normative: true
reviewed: null
links:
- SYS-007
asil: B
---
# SWE-014: Overcurrent-Cutoff
Bei Motorstrom > 8 A laenger als 100 ms muss der Actuator-Driver den Motor abschalten und einen DTC P0xxx setzen.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Klemmkraft-Schaetzung aus Strom-Profil'
level: 1.15
normative: true
reviewed: null
links:
- SYS-007
asil: B
---
# SWE-015: Klemmkraft-Schaetzung aus Strom-Profil
Der Actuator-Driver muss die erreichte Klemmkraft aus dem Stromverlauf bei Apply schaetzen (Modell: F = k * I_peak).
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'UDS RoutineControl 0x31 fuer Service-Release'
level: 1.16
normative: true
reviewed: null
links:
- SYS-008
asil: QM
---
# SWE-016: UDS RoutineControl 0x31 fuer Service-Release
Service-Mode wird ueber UDS RoutineControl Service 0x31, Routine-ID 0x0301 aktiviert. Bedingung: Fahrzeug muss stillstehen.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Service-Mode-Indikator'
level: 1.17
normative: true
reviewed: null
links:
- SYS-008
asil: QM
---
# SWE-017: Service-Mode-Indikator
Im Service-Mode muss die EPB-LED am Schalter mit 2 Hz blinken.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'UDS Service 0x19 ReadDTC'
level: 1.18
normative: true
reviewed: null
links:
- SYS-009
asil: QM
---
# SWE-018: UDS Service 0x19 ReadDTC
Das System muss alle gespeicherten DTCs ueber Service 0x19 (Subfunktion 0x02 reportDTCByStatusMask) ausgeben.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'UDS Service 0x22 ReadDataByIdentifier'
level: 1.19
normative: true
reviewed: null
links:
- SYS-009
asil: QM
---
# SWE-019: UDS Service 0x22 ReadDataByIdentifier
Folgende DIDs muessen lesbar sein: 0xF187 (SW-Version), 0x0301 (Klemmkraft links), 0x0302 (Klemmkraft rechts).
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'LED-Steuerung'
level: 1.20
normative: true
reviewed: null
links:
- SYS-010
asil: QM
---
# SWE-020: LED-Steuerung
Apply-aktiv: LED dauerleuchtend. Release: LED aus. Fehler: LED blinkt 4 Hz. Service-Mode: LED blinkt 2 Hz.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'CAN-Status-Frame'
level: 1.21
normative: true
reviewed: null
links:
- SYS-010
asil: QM
---
# SWE-021: CAN-Status-Frame
Status-Frame 0x3A0 mit 50 Hz: Byte 0 = Status (0=Released, 1=Applied, 2=Applying, 3=Releasing, 0xFF=Error), Byte 1-2 = Klemmkraft links, Byte 3-4 = Klemmkraft rechts.
+17
View File
@@ -0,0 +1,17 @@
---
active: true
derived: false
header: 'Stillstands-Erkennung aus Wheel Speeds'
level: 1.22
normative: true
reviewed: null
links:
- SYS-001
- SYS-002
- SYS-006
asil: B
---
# SWE-022: Stillstands-Erkennung aus Wheel Speeds
Stillstand ist erkannt, wenn alle 4 Wheel-Speed-Signale fuer mindestens 200 ms unter 0.5 km/h liegen.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Wheel Speed Plausibilisierung'
level: 1.23
normative: true
reviewed: null
links:
- SYS-007
asil: B
---
# SWE-023: Wheel Speed Plausibilisierung
Spreizung der Wheel-Speed-Signale: bei Geradeaus-Fahrt darf die Differenz nicht > 3 km/h sein. Andernfalls Sensor-Fehler-DTC.
+15
View File
@@ -0,0 +1,15 @@
---
active: true
derived: false
header: 'Inclinometer Tiefpass-Filter'
level: 1.24
normative: true
reviewed: null
links:
- SYS-005
asil: B
---
# SWE-024: Inclinometer Tiefpass-Filter
Das Roh-Neigungssignal muss mit einem Tiefpass 1. Ordnung (Zeitkonstante 200 ms) gefiltert werden, bevor es zur Hill-Hold-Bewertung verwendet wird.
+16
View File
@@ -0,0 +1,16 @@
---
active: true
derived: false
header: 'Switch-Debouncing'
level: 1.25
normative: true
reviewed: null
links:
- SYS-002
- SYS-003
asil: QM
---
# SWE-025: Switch-Debouncing
Der EPB-Schalter muss mit einer Entprell-Zeit von 50 ms entprellt werden. Stabiler Pegel = Eingangssignal fuer Apply-Controller.
+16
View File
@@ -0,0 +1,16 @@
---
active: true
derived: false
header: 'Halten der Parkbremse im Stillstand'
level: 1.1
normative: true
reviewed: null
links: []
asil: D
---
# SYS-001: Halten der Parkbremse im Stillstand
Wenn die Parkbremse aktiviert ist und das Fahrzeug stillsteht, muss das EPB-System die mechanische Klemmkraft an beiden hinteren Bremssaetteln aufrecht erhalten, bis ein Loesen ausdruecklich angefordert wird. Sicherheitsziel: SG-01.
**Verifikation:** SiL-Test mit Auf-/Ab-Hangelung, Klemmkraftmessung.
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'Apply auf Fahrer-Anforderung'
level: 1.2
normative: true
reviewed: null
links: []
asil: D
---
# SYS-002: Apply auf Fahrer-Anforderung
Bei Betaetigung des EPB-Schalters in Apply-Richtung muss das System innerhalb von 800 ms die Parkbremse anlegen, sofern die Voraussetzungen erfuellt sind (Stillstand oder Geschwindigkeit unter 5 km/h). Sicherheitsziel: SG-01.
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'Release auf Fahrer-Anforderung'
level: 1.3
normative: true
reviewed: null
links: []
asil: B
---
# SYS-003: Release auf Fahrer-Anforderung
Bei Betaetigung des EPB-Schalters in Release-Richtung muss das System die Parkbremse loesen, sofern die folgenden Voraussetzungen erfuellt sind: Motor laeuft, Fahrer betaetigt Bremspedal, Gang ist eingelegt. Maximalzeit fuer Loesen: 1500 ms.
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'Auto-Apply bei Motor-Aus'
level: 1.4
normative: true
reviewed: null
links: []
asil: D
---
# SYS-004: Auto-Apply bei Motor-Aus
Wenn der Motor ausgeschaltet wird und das Fahrzeug stillsteht und keine Parkbremse aktiv ist, muss das System die Parkbremse spaetestens 2 s nach Erkennung Motor-Aus automatisch anlegen. Sicherheitsziel: SG-01.
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'Hill-Hold am Berg'
level: 1.5
normative: true
reviewed: null
links: []
asil: D
---
# SYS-005: Hill-Hold am Berg
Bei aktivem Hill-Hold (Fahrzeug steht am Hang mit Neigung > 5%, Fahrer betaetigt Bremspedal) uebernimmt das EPB-System die Bremskraft beim Loesen des Bremspedals und haelt diese, bis die Anfahrt erkannt wird. Sicherheitsziel: SG-01.
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'Auto-Release beim Anfahren (Drive-Away-Assist)'
level: 1.6
normative: true
reviewed: null
links: []
asil: B
---
# SYS-006: Auto-Release beim Anfahren (Drive-Away-Assist)
Wenn die Parkbremse aktiv ist und der Fahrer Anfahrabsicht zeigt (Gaspedal-Betaetigung bei eingelegtem Gang), muss das System die Parkbremse innerhalb von 500 ms loesen. Voraussetzung: alle Sicherheitskriterien (Fahrertuer geschlossen, Sicherheitsgurt) erfuellt.
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'Aktor-Stromueberwachung'
level: 1.7
normative: true
reviewed: null
links: []
asil: B
---
# SYS-007: Aktor-Stromueberwachung
Das System muss den Motorstrom jedes Aktors mit mindestens 1 kHz ueberwachen und bei Ueberschreitung von 8 A fuer mehr als 100 ms den Aktor abschalten und einen DTC setzen. Sicherheitsziel: SG-03.
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'Service-Modus fuer Werkstatt'
level: 1.8
normative: true
reviewed: null
links: []
asil: QM
---
# SYS-008: Service-Modus fuer Werkstatt
Das System muss ueber UDS RoutineControl (Service 0x31) einen Service-Modus bereitstellen, in dem die Aktoren manuell in Wartungs-Position gefahren werden koennen (z.B. fuer Bremsbelag-Wechsel).
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'UDS-Diagnose'
level: 1.9
normative: true
reviewed: null
links: []
asil: QM
---
# SYS-009: UDS-Diagnose
Das System muss UDS-Diagnose nach ISO 14229 bereitstellen: ReadDTC (0x19), ReadDataByIdentifier (0x22), RoutineControl (0x31), ECUReset (0x11). Tester-Adresse 0x712, Antwort-Adresse 0x71A.
+14
View File
@@ -0,0 +1,14 @@
---
active: true
derived: false
header: 'HMI-Statusanzeige'
level: 1.10
normative: true
reviewed: null
links: []
asil: QM
---
# SYS-010: HMI-Statusanzeige
Der EPB-Status muss dem Fahrer signalisiert werden: LED am Schalter (an = Apply, aus = Release, blinkend = Fehler) sowie Text im Kombi-Display via CAN-Bus (Frame-ID 0x3A0, 50 Hz).
+125
View File
@@ -0,0 +1,125 @@
/**
* @file actuator_driver.c
* @brief Implementierung der EPB-Aktor-Ansteuerung.
*
* @arch SWA-003
* @reqs SWE-006 SWE-013 SWE-014 SWE-015
*
* ASIL: B.
*/
#include "actuator_driver.h"
typedef struct {
ActuatorStatus status;
uint16_t over_ms; /* Millisekunden ueber Strom-Limit (zaehlt in 1 kHz ISR) */
} ActuatorCtx;
static ActuatorCtx s_ctx[ACTUATOR_COUNT];
static bool is_valid_id(ActuatorId id)
{
return (id == ACTUATOR_LEFT) || (id == ACTUATOR_RIGHT);
}
EpbStatus actuator_init(void)
{
for (uint8_t i = 0U; i < ACTUATOR_COUNT; ++i) {
s_ctx[i].status.direction = ACT_DIR_STOP;
s_ctx[i].status.pwm_percent = 0U;
s_ctx[i].status.current_ma = 0U;
s_ctx[i].status.peak_current_ma = 0U;
s_ctx[i].status.clamping_force_n = 0U;
s_ctx[i].status.overcurrent = false;
s_ctx[i].status.last_error = EPB_OK;
s_ctx[i].over_ms = 0U;
}
return EPB_OK;
}
EpbStatus actuator_apply(ActuatorId id, uint8_t pwm_percent)
{
if (!is_valid_id(id)) {
return EPB_EINVAL;
}
if (pwm_percent > 100U) {
return EPB_EINVAL;
}
if (s_ctx[id].status.overcurrent) {
return EPB_EOVERCURRENT;
}
s_ctx[id].status.direction = ACT_DIR_APPLY;
s_ctx[id].status.pwm_percent = pwm_percent;
s_ctx[id].status.peak_current_ma = 0U;
return EPB_OK;
}
EpbStatus actuator_release(ActuatorId id, uint8_t pwm_percent)
{
if (!is_valid_id(id)) {
return EPB_EINVAL;
}
if (pwm_percent > 100U) {
return EPB_EINVAL;
}
if (s_ctx[id].status.overcurrent) {
return EPB_EOVERCURRENT;
}
s_ctx[id].status.direction = ACT_DIR_RELEASE;
s_ctx[id].status.pwm_percent = pwm_percent;
return EPB_OK;
}
EpbStatus actuator_stop(ActuatorId id)
{
if (!is_valid_id(id)) {
return EPB_EINVAL;
}
s_ctx[id].status.direction = ACT_DIR_STOP;
s_ctx[id].status.pwm_percent = 0U;
return EPB_OK;
}
ActuatorStatus actuator_get_status(ActuatorId id)
{
if (!is_valid_id(id)) {
ActuatorStatus empty = {0};
empty.last_error = EPB_EINVAL;
return empty;
}
return s_ctx[id].status;
}
void actuator_isr_1khz(ActuatorId id, uint16_t current_sample_ma)
{
if (!is_valid_id(id)) {
return;
}
s_ctx[id].status.current_ma = current_sample_ma;
if (current_sample_ma > s_ctx[id].status.peak_current_ma) {
s_ctx[id].status.peak_current_ma = current_sample_ma;
}
/* SWE-014: Overcurrent-Cutoff bei > 8 A fuer > 100 ms */
if (current_sample_ma > ACT_OVERCURRENT_LIMIT_MA) {
if (s_ctx[id].over_ms < UINT16_MAX) {
++s_ctx[id].over_ms;
}
if (s_ctx[id].over_ms >= ACT_OVERCURRENT_WINDOW_MS) {
s_ctx[id].status.direction = ACT_DIR_STOP;
s_ctx[id].status.pwm_percent = 0U;
s_ctx[id].status.overcurrent = true;
s_ctx[id].status.last_error = EPB_EOVERCURRENT;
}
} else {
s_ctx[id].over_ms = 0U;
}
/* SWE-015: Klemmkraft aus Peak-Strom schaetzen (nur bei Apply). */
if (s_ctx[id].status.direction == ACT_DIR_APPLY) {
const uint32_t force = ((uint32_t)s_ctx[id].status.peak_current_ma
* ACT_FORCE_PER_AMP_N) / 1000U;
s_ctx[id].status.clamping_force_n =
(force > UINT16_MAX) ? UINT16_MAX : (uint16_t)force;
}
}
+49
View File
@@ -0,0 +1,49 @@
/**
* @file actuator_driver.h
* @brief Low-Level-Ansteuerung der EPB-Aktoren.
*
* @arch SWA-003
* @reqs SWE-006 SWE-013 SWE-014 SWE-015
*
* ASIL: B
*/
#ifndef ACTUATOR_DRIVER_H
#define ACTUATOR_DRIVER_H
#include "epb_types.h"
typedef enum {
ACT_DIR_STOP = 0,
ACT_DIR_APPLY = 1,
ACT_DIR_RELEASE = 2
} ActuatorDirection;
typedef struct {
ActuatorDirection direction;
uint8_t pwm_percent; /* 0..100 */
uint16_t current_ma; /* aktueller Motorstrom */
uint16_t peak_current_ma;
uint16_t clamping_force_n; /* geschaetzt aus Strom */
bool overcurrent;
EpbStatus last_error;
} ActuatorStatus;
/** Strom-Limit (Spec) und Zeitfenster fuer Cutoff (Spec). */
#define ACT_OVERCURRENT_LIMIT_MA 8000U
#define ACT_OVERCURRENT_WINDOW_MS 100U
#define ACT_FORCE_PER_AMP_N 2500U /* lineare Naeherung */
EpbStatus actuator_init(void);
EpbStatus actuator_apply(ActuatorId id, uint8_t pwm_percent);
EpbStatus actuator_release(ActuatorId id, uint8_t pwm_percent);
EpbStatus actuator_stop(ActuatorId id);
ActuatorStatus actuator_get_status(ActuatorId id);
/**
* @brief ISR-Hook fuer Strom-Sampling. Wird mit 1 kHz aufgerufen.
*
* Fuer Tests durch Test-Doubles ersetzt.
*/
void actuator_isr_1khz(ActuatorId id, uint16_t current_sample_ma);
#endif /* ACTUATOR_DRIVER_H */
+153
View File
@@ -0,0 +1,153 @@
/**
* @file apply_controller.c
* @brief Apply/Hold/Release State Machine.
*
* @arch SWA-002
* @reqs SWE-001 SWE-002 SWE-003 SWE-004
*
* ASIL: D. Diese Komponente ist die sicherheitskritische Kernlogik.
* Aenderungen erfordern Technical Review mit 2 Approvals.
*/
#include <stddef.h>
#include "apply_controller.h"
#include "actuator_driver.h"
typedef struct {
EpbState state;
uint8_t step_in_state; /* 50ms-Ticks im aktuellen State */
EpbStatus last_error;
uint32_t step_count; /* Watchdog-Alive-Counter */
} ApplyCtx;
static ApplyCtx s_ctx;
static void enter_state(EpbState new_state)
{
s_ctx.state = new_state;
s_ctx.step_in_state = 0U;
}
static bool release_preconditions_ok(const ApplyInputs* in)
{
/* @reqs SWE-005 (Release-Voraussetzungen) — hier konsumiert */
return in->engine_running
&& in->brake_pedal_pressed
&& in->gear_engaged;
}
static bool apply_request_present(const ApplyInputs* in)
{
return (in->sw_state == SWITCH_APPLY) || in->safety_apply_request;
}
static bool release_request_present(const ApplyInputs* in)
{
return in->sw_state == SWITCH_RELEASE;
}
static uint16_t min_force(const ApplyInputs* in)
{
return (in->left_force_n < in->right_force_n)
? in->left_force_n : in->right_force_n;
}
EpbStatus apply_ctrl_init(void)
{
s_ctx.state = EPB_STATE_RELEASED;
s_ctx.step_in_state = 0U;
s_ctx.last_error = EPB_OK;
s_ctx.step_count = 0U;
return EPB_OK;
}
void apply_ctrl_step_50ms(const ApplyInputs* in)
{
if (in == NULL) {
s_ctx.last_error = EPB_EINVAL;
return;
}
/* SWE-002: Watchdog-Alive-Counter erhoehen */
++s_ctx.step_count;
if (s_ctx.step_in_state < UINT8_MAX) {
++s_ctx.step_in_state;
}
switch (s_ctx.state) {
case EPB_STATE_RELEASED:
if (apply_request_present(in) && in->standstill) {
(void)actuator_apply(ACTUATOR_LEFT, 80U);
(void)actuator_apply(ACTUATOR_RIGHT, 80U);
enter_state(EPB_STATE_APPLYING);
}
break;
case EPB_STATE_APPLYING:
/* SWE-004: Klemmkraft-Erreichen pruefen */
if (min_force(in) >= APPLY_TARGET_FORCE_N) {
(void)actuator_stop(ACTUATOR_LEFT);
(void)actuator_stop(ACTUATOR_RIGHT);
enter_state(EPB_STATE_APPLIED);
} else if (s_ctx.step_in_state >= APPLY_TIMEOUT_50MS) {
s_ctx.last_error = EPB_ETIMEOUT;
(void)actuator_stop(ACTUATOR_LEFT);
(void)actuator_stop(ACTUATOR_RIGHT);
enter_state(EPB_STATE_ERROR);
}
break;
case EPB_STATE_APPLIED:
/* SWE-001: Klemmkraft halten — bei Unterschreitung nachregeln */
if (min_force(in) < (APPLY_TARGET_FORCE_N - HOLD_TOLERANCE_N)) {
(void)actuator_apply(ACTUATOR_LEFT, 60U);
(void)actuator_apply(ACTUATOR_RIGHT, 60U);
enter_state(EPB_STATE_APPLYING);
break;
}
if (release_request_present(in) && release_preconditions_ok(in)) {
(void)actuator_release(ACTUATOR_LEFT, 80U);
(void)actuator_release(ACTUATOR_RIGHT, 80U);
enter_state(EPB_STATE_RELEASING);
}
break;
case EPB_STATE_RELEASING:
if (min_force(in) < HOLD_TOLERANCE_N) {
(void)actuator_stop(ACTUATOR_LEFT);
(void)actuator_stop(ACTUATOR_RIGHT);
enter_state(EPB_STATE_RELEASED);
} else if (s_ctx.step_in_state >= (APPLY_TIMEOUT_50MS - 6U)) {
s_ctx.last_error = EPB_ETIMEOUT;
(void)actuator_stop(ACTUATOR_LEFT);
(void)actuator_stop(ACTUATOR_RIGHT);
enter_state(EPB_STATE_ERROR);
}
break;
case EPB_STATE_ERROR:
default:
/* Auf Reset warten; sicherer Zustand ist Apply, also kein Release */
if (!apply_request_present(in) && !release_request_present(in)) {
s_ctx.last_error = EPB_OK;
enter_state(EPB_STATE_RELEASED);
}
break;
}
}
EpbState apply_ctrl_get_state(void)
{
return s_ctx.state;
}
EpbStatus apply_ctrl_last_error(void)
{
return s_ctx.last_error;
}
uint32_t apply_ctrl_get_step_count(void)
{
return s_ctx.step_count;
}
+43
View File
@@ -0,0 +1,43 @@
/**
* @file apply_controller.h
* @brief Apply/Hold/Release-Steuerung der EPB.
*
* @arch SWA-002
* @reqs SWE-001 SWE-002 SWE-003 SWE-004
*
* ASIL: D
*/
#ifndef APPLY_CONTROLLER_H
#define APPLY_CONTROLLER_H
#include "epb_types.h"
typedef struct {
SwitchState sw_state; /* aus SwitchDebouncer */
bool standstill; /* aus Wheel-Speed-Plausi */
bool engine_running;
bool brake_pedal_pressed;
bool gear_engaged;
bool safety_apply_request; /* aus SafetyManager */
uint16_t left_force_n;
uint16_t right_force_n;
} ApplyInputs;
#define APPLY_TARGET_FORCE_N 12000U /* Ziel-Klemmkraft je Aktor */
#define APPLY_TIMEOUT_50MS 30U /* 30 * 50ms = 1500 ms */
#define HOLD_TOLERANCE_N 1200U /* 10% von Ziel */
EpbStatus apply_ctrl_init(void);
/**
* @brief Step-Funktion 50 ms.
*
* @reqs SWE-001 SWE-002 SWE-003 SWE-004
*/
void apply_ctrl_step_50ms(const ApplyInputs* in);
EpbState apply_ctrl_get_state(void);
EpbStatus apply_ctrl_last_error(void);
uint32_t apply_ctrl_get_step_count(void); /* Watchdog-Alive-Counter */
#endif /* APPLY_CONTROLLER_H */
+47
View File
@@ -0,0 +1,47 @@
/**
* @file epb_types.h
* @brief Gemeinsame Typen fuer die EPB-Software.
*
* @arch SA-001
*/
#ifndef EPB_TYPES_H
#define EPB_TYPES_H
#include <stdbool.h>
#include <stdint.h>
typedef enum {
EPB_OK = 0,
EPB_EINVAL = 1,
EPB_ETIMEOUT = 2,
EPB_ENOSPACE = 3,
EPB_EHARDWARE = 4,
EPB_EOVERCURRENT = 5
} EpbStatus;
typedef enum {
EPB_STATE_RELEASED = 0,
EPB_STATE_APPLYING = 1,
EPB_STATE_APPLIED = 2,
EPB_STATE_RELEASING = 3,
EPB_STATE_ERROR = 0xFF
} EpbState;
typedef enum {
ACTUATOR_LEFT = 0,
ACTUATOR_RIGHT = 1,
ACTUATOR_COUNT = 2
} ActuatorId;
typedef enum {
SWITCH_NEUTRAL = 0,
SWITCH_APPLY = 1,
SWITCH_RELEASE = 2
} SwitchState;
typedef struct {
uint8_t apply_raw : 1;
uint8_t release_raw : 1;
} SwitchRaw;
#endif /* EPB_TYPES_H */
+18
View File
@@ -0,0 +1,18 @@
# Stubs — nicht implementierte Komponenten
Diese Komponenten sind in der Software-Architektur (`arch/swe/`) vollstaendig
spezifiziert, aber in dieser Demo nicht ausimplementiert. Sie zeigen nur die
Header-Schnittstellen.
Im Real-Projekt wuerden hier vollstaendige `.c`-Implementierungen plus
Unit-Tests stehen.
| Komponente | ASIL | SWA |
|-------------------------|------|-----------|
| Safety Manager | D | SWA-001 |
| Wheel Speed Plausi | B | SWA-004 |
| Inclinometer Filter | B | SWA-005 |
| Display Manager | QM | SWA-007 |
| Diagnostic Manager | QM | SWA-008 |
| Service Mode | QM | SWA-009 |
| Logger | QM | SWA-010 |
+19
View File
@@ -0,0 +1,19 @@
/**
* @file diag_manager.h
* @brief UDS-Diagnose nach ISO 14229.
*
* @arch SWA-008
* @reqs SWE-018 SWE-019
*
* ASIL: QM. STUB.
*/
#ifndef DIAG_MANAGER_H
#define DIAG_MANAGER_H
#include "../epb_types.h"
EpbStatus diag_init(void);
void diag_handle_request(const uint8_t* data, uint16_t len);
void diag_set_dtc(uint16_t dtc_id);
#endif
+19
View File
@@ -0,0 +1,19 @@
/**
* @file display_manager.h
* @brief LED + CAN-Status-Frame Steuerung.
*
* @arch SWA-007
* @reqs SWE-020 SWE-021
*
* ASIL: QM. STUB.
*/
#ifndef DISPLAY_MANAGER_H
#define DISPLAY_MANAGER_H
#include "../epb_types.h"
EpbStatus display_init(void);
void display_set_status(EpbState s);
void display_step_20ms(void);
#endif
+19
View File
@@ -0,0 +1,19 @@
/**
* @file inclinometer.h
* @brief Inclinometer-Tiefpass-Filter.
*
* @arch SWA-005
* @reqs SWE-024
*
* ASIL: B. STUB.
*/
#ifndef INCLINOMETER_H
#define INCLINOMETER_H
#include "../epb_types.h"
EpbStatus inclino_init(void);
void inclino_step_10ms(int16_t raw_mdeg);
float inclino_get_grade_percent(void);
#endif
+24
View File
@@ -0,0 +1,24 @@
/**
* @file logger.h
* @brief Logger — Ringpuffer + EEPROM-Persistenz.
*
* @arch SWA-010
*
* ASIL: QM. STUB.
*/
#ifndef LOGGER_H
#define LOGGER_H
#include "../epb_types.h"
typedef enum {
LOG_DEBUG = 0,
LOG_INFO,
LOG_WARN,
LOG_ERROR
} LogLevel;
EpbStatus log_init(void);
void log_event(LogLevel lvl, uint16_t event_id, uint32_t param);
#endif
+27
View File
@@ -0,0 +1,27 @@
/**
* @file safety_manager.h
* @brief Safety Manager — Hill-Hold + Auto-Apply Logik.
*
* @arch SWA-001
* @reqs SWE-007 SWE-008 SWE-009 SWE-010
*
* ASIL: D. STUB — nicht implementiert in dieser Demo.
*/
#ifndef SAFETY_MANAGER_H
#define SAFETY_MANAGER_H
#include "../epb_types.h"
typedef struct {
bool engine_running;
bool brake_pedal_pressed;
float vehicle_speed_kmh;
float grade_percent;
EpbState current_state;
} SafetyInputs;
EpbStatus safety_mgr_init(void);
void safety_mgr_step_50ms(const SafetyInputs* in);
bool safety_mgr_apply_requested(void);
#endif
+20
View File
@@ -0,0 +1,20 @@
/**
* @file service_mode.h
* @brief Service-Modus fuer Werkstatt (Bremsbelag-Wechsel).
*
* @arch SWA-009
* @reqs SWE-016 SWE-017
*
* ASIL: QM. STUB.
*/
#ifndef SERVICE_MODE_H
#define SERVICE_MODE_H
#include "../epb_types.h"
EpbStatus service_mode_init(void);
EpbStatus service_mode_activate(void);
EpbStatus service_mode_deactivate(void);
bool service_mode_is_active(void);
#endif
+27
View File
@@ -0,0 +1,27 @@
/**
* @file wheel_speed_plausi.h
* @brief Wheel-Speed-Plausibilisierung + Stillstands-Erkennung.
*
* @arch SWA-004
* @reqs SWE-022 SWE-023
*
* ASIL: B. STUB.
*/
#ifndef WHEEL_SPEED_PLAUSI_H
#define WHEEL_SPEED_PLAUSI_H
#include "../epb_types.h"
typedef struct {
float fl_kmh;
float fr_kmh;
float rl_kmh;
float rr_kmh;
} WheelInputs;
EpbStatus wheel_speed_init(void);
void wheel_speed_step_10ms(const WheelInputs* in);
bool wheel_speed_is_standstill(void);
float wheel_speed_get_vehicle(void);
#endif
+60
View File
@@ -0,0 +1,60 @@
/**
* @file switch_debouncer.c
* @brief Implementierung des EPB-Schalter-Debouncers.
*
* @arch SWA-006
* @reqs SWE-025
*
* ASIL: QM.
*/
#include "switch_debouncer.h"
typedef struct {
SwitchState current;
SwitchState candidate;
uint8_t candidate_count;
} DebouncerCtx;
static DebouncerCtx s_ctx;
static SwitchState raw_to_candidate(SwitchRaw raw)
{
if (raw.apply_raw && !raw.release_raw) {
return SWITCH_APPLY;
}
if (raw.release_raw && !raw.apply_raw) {
return SWITCH_RELEASE;
}
return SWITCH_NEUTRAL;
}
EpbStatus switch_init(void)
{
s_ctx.current = SWITCH_NEUTRAL;
s_ctx.candidate = SWITCH_NEUTRAL;
s_ctx.candidate_count = 0U;
return EPB_OK;
}
void switch_step_10ms(SwitchRaw raw)
{
const SwitchState observed = raw_to_candidate(raw);
if (observed == s_ctx.candidate) {
if (s_ctx.candidate_count < SWITCH_DEBOUNCE_SAMPLES) {
++s_ctx.candidate_count;
}
} else {
s_ctx.candidate = observed;
s_ctx.candidate_count = 1U;
}
if (s_ctx.candidate_count >= SWITCH_DEBOUNCE_SAMPLES) {
s_ctx.current = s_ctx.candidate;
}
}
SwitchState switch_get_state(void)
{
return s_ctx.current;
}
+22
View File
@@ -0,0 +1,22 @@
/**
* @file switch_debouncer.h
* @brief EPB-Schalter mit Software-Entprellung.
*
* @arch SWA-006
* @reqs SWE-025
*
* ASIL: QM
*/
#ifndef SWITCH_DEBOUNCER_H
#define SWITCH_DEBOUNCER_H
#include "epb_types.h"
/** Step-Zyklus 10 ms. Debounce-Schwelle 50 ms (5 stabile Samples). */
#define SWITCH_DEBOUNCE_SAMPLES 5
EpbStatus switch_init(void);
void switch_step_10ms(SwitchRaw raw);
SwitchState switch_get_state(void);
#endif /* SWITCH_DEBOUNCER_H */
+157
View File
@@ -0,0 +1,157 @@
/**
* @file test_actuator_driver.c
* @brief Unit-Tests fuer den Actuator-Driver.
*
* @reqs SWE-006 SWE-013 SWE-014 SWE-015
* @arch SWA-003
*/
#include "../unit_test_framework.h"
#include "../../src/actuator_driver.h"
TestStats g_test_stats = {0, 0};
static void test_init(void)
{
TEST_BEGIN("init -> beide Aktoren STOP, 0 mA");
ASSERT_EQ(actuator_init(), EPB_OK);
ActuatorStatus l = actuator_get_status(ACTUATOR_LEFT);
ActuatorStatus r = actuator_get_status(ACTUATOR_RIGHT);
ASSERT_EQ(l.direction, ACT_DIR_STOP);
ASSERT_EQ(r.direction, ACT_DIR_STOP);
ASSERT_EQ(l.current_ma, 0);
TEST_END();
}
static void test_apply_invalid_id(void)
{
TEST_BEGIN("apply: invalid ID -> EINVAL");
(void)actuator_init();
ASSERT_EQ(actuator_apply((ActuatorId)42, 50), EPB_EINVAL);
TEST_END();
}
static void test_apply_pwm_out_of_range(void)
{
TEST_BEGIN("apply: PWM > 100% -> EINVAL");
(void)actuator_init();
ASSERT_EQ(actuator_apply(ACTUATOR_LEFT, 101), EPB_EINVAL);
TEST_END();
}
static void test_apply_normal(void)
{
TEST_BEGIN("apply normal -> direction=APPLY, pwm=80");
(void)actuator_init();
ASSERT_EQ(actuator_apply(ACTUATOR_LEFT, 80), EPB_OK);
ActuatorStatus s = actuator_get_status(ACTUATOR_LEFT);
ASSERT_EQ(s.direction, ACT_DIR_APPLY);
ASSERT_EQ(s.pwm_percent, 80);
TEST_END();
}
static void test_release_normal(void)
{
TEST_BEGIN("release normal -> direction=RELEASE");
(void)actuator_init();
ASSERT_EQ(actuator_release(ACTUATOR_LEFT, 70), EPB_OK);
ActuatorStatus s = actuator_get_status(ACTUATOR_LEFT);
ASSERT_EQ(s.direction, ACT_DIR_RELEASE);
TEST_END();
}
static void test_isr_samples_current(void)
{
TEST_BEGIN("ISR-Sample setzt current_ma und peak");
(void)actuator_init();
(void)actuator_apply(ACTUATOR_LEFT, 80);
actuator_isr_1khz(ACTUATOR_LEFT, 3000);
ActuatorStatus s = actuator_get_status(ACTUATOR_LEFT);
ASSERT_EQ(s.current_ma, 3000);
ASSERT_EQ(s.peak_current_ma, 3000);
TEST_END();
}
static void test_overcurrent_cutoff_after_100ms(void)
{
TEST_BEGIN("Overcurrent > 8 A fuer 100 ms -> STOP + overcurrent flag");
(void)actuator_init();
(void)actuator_apply(ACTUATOR_LEFT, 80);
/* 100 Samples (= 100 ms bei 1 kHz) ueber 8 A */
for (int i = 0; i < 100; ++i) {
actuator_isr_1khz(ACTUATOR_LEFT, 8500);
}
ActuatorStatus s = actuator_get_status(ACTUATOR_LEFT);
ASSERT_EQ(s.direction, ACT_DIR_STOP);
ASSERT_TRUE(s.overcurrent);
ASSERT_EQ(s.last_error, EPB_EOVERCURRENT);
TEST_END();
}
static void test_overcurrent_below_window_no_cutoff(void)
{
TEST_BEGIN("Overcurrent < 100 ms loest noch nicht aus");
(void)actuator_init();
(void)actuator_apply(ACTUATOR_LEFT, 80);
for (int i = 0; i < 50; ++i) {
actuator_isr_1khz(ACTUATOR_LEFT, 8500);
}
ActuatorStatus s = actuator_get_status(ACTUATOR_LEFT);
ASSERT_EQ(s.direction, ACT_DIR_APPLY);
ASSERT_TRUE(!s.overcurrent);
TEST_END();
}
static void test_overcurrent_blocks_subsequent_apply(void)
{
TEST_BEGIN("nach Overcurrent: weiterer apply -> EOVERCURRENT");
(void)actuator_init();
(void)actuator_apply(ACTUATOR_LEFT, 80);
for (int i = 0; i < 120; ++i) {
actuator_isr_1khz(ACTUATOR_LEFT, 9000);
}
EpbStatus rc = actuator_apply(ACTUATOR_LEFT, 80);
ASSERT_EQ(rc, EPB_EOVERCURRENT);
TEST_END();
}
static void test_clamping_force_estimate(void)
{
TEST_BEGIN("Klemmkraft-Schaetzung aus Peak-Strom (SWE-015)");
(void)actuator_init();
(void)actuator_apply(ACTUATOR_LEFT, 80);
actuator_isr_1khz(ACTUATOR_LEFT, 4000); /* 4 A Peak */
actuator_isr_1khz(ACTUATOR_LEFT, 3500); /* danach niedriger */
ActuatorStatus s = actuator_get_status(ACTUATOR_LEFT);
/* F = (4000 * 2500) / 1000 = 10000 N */
ASSERT_EQ(s.clamping_force_n, 10000);
TEST_END();
}
static void test_stop_clears_pwm(void)
{
TEST_BEGIN("stop -> direction=STOP, pwm=0");
(void)actuator_init();
(void)actuator_apply(ACTUATOR_LEFT, 80);
(void)actuator_stop(ACTUATOR_LEFT);
ActuatorStatus s = actuator_get_status(ACTUATOR_LEFT);
ASSERT_EQ(s.direction, ACT_DIR_STOP);
ASSERT_EQ(s.pwm_percent, 0);
TEST_END();
}
int main(void)
{
printf("== test_actuator_driver ==\n");
test_init();
test_apply_invalid_id();
test_apply_pwm_out_of_range();
test_apply_normal();
test_release_normal();
test_isr_samples_current();
test_overcurrent_cutoff_after_100ms();
test_overcurrent_below_window_no_cutoff();
test_overcurrent_blocks_subsequent_apply();
test_clamping_force_estimate();
test_stop_clears_pwm();
TEST_SUMMARY();
}
+240
View File
@@ -0,0 +1,240 @@
/**
* @file test_apply_controller.c
* @brief Unit-Tests fuer den Apply-Controller (ASIL-D Kern).
*
* @reqs SWE-001 SWE-002 SWE-003 SWE-004
* @arch SWA-002
*/
#include "../unit_test_framework.h"
#include "../../src/apply_controller.h"
#include "../../src/actuator_driver.h"
TestStats g_test_stats = {0, 0};
static ApplyInputs make_idle_inputs(void)
{
ApplyInputs in = {0};
in.sw_state = SWITCH_NEUTRAL;
in.standstill = true;
in.engine_running = true;
in.brake_pedal_pressed = true;
in.gear_engaged = true;
in.safety_apply_request = false;
in.left_force_n = 0;
in.right_force_n = 0;
return in;
}
static void apply_full_cycle_until_state(ApplyInputs* in, EpbState target, int max_steps)
{
for (int i = 0; i < max_steps; ++i) {
apply_ctrl_step_50ms(in);
if (apply_ctrl_get_state() == target) {
return;
}
}
}
static void test_init(void)
{
TEST_BEGIN("init -> RELEASED");
(void)actuator_init();
ASSERT_EQ(apply_ctrl_init(), EPB_OK);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASED);
TEST_END();
}
static void test_null_input(void)
{
TEST_BEGIN("step_50ms(NULL) -> last_error=EINVAL");
(void)actuator_init();
(void)apply_ctrl_init();
apply_ctrl_step_50ms(NULL);
ASSERT_EQ(apply_ctrl_last_error(), EPB_EINVAL);
TEST_END();
}
static void test_apply_request_starts_applying(void)
{
TEST_BEGIN("Apply-Request bei Stillstand -> APPLYING");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.sw_state = SWITCH_APPLY;
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLYING);
TEST_END();
}
static void test_no_apply_without_standstill(void)
{
TEST_BEGIN("Apply-Request ohne Stillstand wird ignoriert");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.standstill = false;
in.sw_state = SWITCH_APPLY;
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASED);
TEST_END();
}
static void test_applying_reaches_applied_on_target_force(void)
{
TEST_BEGIN("APPLYING -> APPLIED wenn beide Klemmkraefte >= Target");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.sw_state = SWITCH_APPLY;
apply_ctrl_step_50ms(&in); /* -> APPLYING */
in.sw_state = SWITCH_NEUTRAL;
in.left_force_n = 12000;
in.right_force_n = 12000;
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLIED);
TEST_END();
}
static void test_applying_timeout_to_error(void)
{
TEST_BEGIN("APPLYING-Timeout (>30 Steps) -> ERROR");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.sw_state = SWITCH_APPLY;
apply_ctrl_step_50ms(&in); /* -> APPLYING */
in.sw_state = SWITCH_NEUTRAL;
/* Klemmkraefte bleiben bei 0 -> Timeout nach 30 Steps */
apply_full_cycle_until_state(&in, EPB_STATE_ERROR, 35);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_ERROR);
ASSERT_EQ(apply_ctrl_last_error(), EPB_ETIMEOUT);
TEST_END();
}
static void test_applied_holds_force(void)
{
TEST_BEGIN("APPLIED haelt Klemmkraft (SWE-001)");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.sw_state = SWITCH_APPLY;
apply_ctrl_step_50ms(&in);
in.sw_state = SWITCH_NEUTRAL;
in.left_force_n = 12000;
in.right_force_n = 12000;
apply_ctrl_step_50ms(&in); /* -> APPLIED */
/* Klemmkraft sinkt auf 10000 (unter Toleranz-Schwelle 10800) */
in.left_force_n = 10000;
in.right_force_n = 10000;
apply_ctrl_step_50ms(&in);
/* Apply-Controller muss nachregeln -> APPLYING */
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLYING);
TEST_END();
}
static void test_release_requires_preconditions(void)
{
TEST_BEGIN("Release ohne Engine wird abgelehnt");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.sw_state = SWITCH_APPLY;
apply_ctrl_step_50ms(&in);
in.sw_state = SWITCH_NEUTRAL;
in.left_force_n = 12000;
in.right_force_n = 12000;
apply_ctrl_step_50ms(&in); /* -> APPLIED */
in.sw_state = SWITCH_RELEASE;
in.engine_running = false;
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLIED);
TEST_END();
}
static void test_release_with_preconditions(void)
{
TEST_BEGIN("Release mit allen Voraussetzungen -> RELEASING -> RELEASED");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.sw_state = SWITCH_APPLY;
apply_ctrl_step_50ms(&in);
in.sw_state = SWITCH_NEUTRAL;
in.left_force_n = 12000;
in.right_force_n = 12000;
apply_ctrl_step_50ms(&in); /* APPLIED */
in.sw_state = SWITCH_RELEASE;
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASING);
in.sw_state = SWITCH_NEUTRAL;
in.left_force_n = 0;
in.right_force_n = 0;
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASED);
TEST_END();
}
static void test_safety_apply_request(void)
{
TEST_BEGIN("Safety-Manager Apply-Request (Hill-Hold/Auto-Apply)");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.sw_state = SWITCH_NEUTRAL;
in.safety_apply_request = true;
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_APPLYING);
TEST_END();
}
static void test_watchdog_alive_counter(void)
{
TEST_BEGIN("Step-Counter steigt monoton (SWE-002 Watchdog-Alive)");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
uint32_t before = apply_ctrl_get_step_count();
apply_ctrl_step_50ms(&in);
apply_ctrl_step_50ms(&in);
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_step_count(), before + 3);
TEST_END();
}
static void test_error_state_recoverable(void)
{
TEST_BEGIN("ERROR -> RELEASED bei neutralem Switch");
(void)actuator_init();
(void)apply_ctrl_init();
ApplyInputs in = make_idle_inputs();
in.sw_state = SWITCH_APPLY;
apply_ctrl_step_50ms(&in);
in.sw_state = SWITCH_NEUTRAL;
apply_full_cycle_until_state(&in, EPB_STATE_ERROR, 35);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_ERROR);
apply_ctrl_step_50ms(&in);
ASSERT_EQ(apply_ctrl_get_state(), EPB_STATE_RELEASED);
TEST_END();
}
int main(void)
{
printf("== test_apply_controller ==\n");
test_init();
test_null_input();
test_apply_request_starts_applying();
test_no_apply_without_standstill();
test_applying_reaches_applied_on_target_force();
test_applying_timeout_to_error();
test_applied_holds_force();
test_release_requires_preconditions();
test_release_with_preconditions();
test_safety_apply_request();
test_watchdog_alive_counter();
test_error_state_recoverable();
TEST_SUMMARY();
}
+91
View File
@@ -0,0 +1,91 @@
/**
* @file test_switch_debouncer.c
* @brief Unit-Tests fuer den Switch-Debouncer.
*
* @reqs SWE-025
* @arch SWA-006
*/
#include "../unit_test_framework.h"
#include "../../src/switch_debouncer.h"
TestStats g_test_stats = {0, 0};
static SwitchRaw mk(bool apply, bool release)
{
SwitchRaw r = {0};
r.apply_raw = apply ? 1U : 0U;
r.release_raw = release ? 1U : 0U;
return r;
}
static void test_init_state_is_neutral(void)
{
TEST_BEGIN("init -> neutral");
(void)switch_init();
ASSERT_EQ(switch_get_state(), SWITCH_NEUTRAL);
TEST_END();
}
static void test_debounce_apply_takes_5_samples(void)
{
TEST_BEGIN("apply needs 5 stable samples (50 ms)");
(void)switch_init();
for (int i = 0; i < 4; ++i) {
switch_step_10ms(mk(true, false));
ASSERT_EQ(switch_get_state(), SWITCH_NEUTRAL);
}
switch_step_10ms(mk(true, false));
ASSERT_EQ(switch_get_state(), SWITCH_APPLY);
TEST_END();
}
static void test_debounce_release_takes_5_samples(void)
{
TEST_BEGIN("release needs 5 stable samples (50 ms)");
(void)switch_init();
for (int i = 0; i < 5; ++i) {
switch_step_10ms(mk(false, true));
}
ASSERT_EQ(switch_get_state(), SWITCH_RELEASE);
TEST_END();
}
static void test_bounce_does_not_change_state(void)
{
TEST_BEGIN("bouncing input keeps current state");
(void)switch_init();
for (int i = 0; i < 5; ++i) {
switch_step_10ms(mk(true, false));
}
ASSERT_EQ(switch_get_state(), SWITCH_APPLY);
/* Bounce: 1 sample apply, 1 sample neutral, 1 sample release ... */
switch_step_10ms(mk(true, false));
switch_step_10ms(mk(false, false));
switch_step_10ms(mk(false, true));
switch_step_10ms(mk(true, false));
switch_step_10ms(mk(false, false));
ASSERT_EQ(switch_get_state(), SWITCH_APPLY);
TEST_END();
}
static void test_both_pressed_is_neutral(void)
{
TEST_BEGIN("both apply+release pressed -> neutral");
(void)switch_init();
for (int i = 0; i < 5; ++i) {
switch_step_10ms(mk(true, true));
}
ASSERT_EQ(switch_get_state(), SWITCH_NEUTRAL);
TEST_END();
}
int main(void)
{
printf("== test_switch_debouncer ==\n");
test_init_state_is_neutral();
test_debounce_apply_takes_5_samples();
test_debounce_release_takes_5_samples();
test_bounce_does_not_change_state();
test_both_pressed_is_neutral();
TEST_SUMMARY();
}
+62
View File
@@ -0,0 +1,62 @@
/**
* @file unit_test_framework.h
* @brief Minimaler Test-Runner fuer demo-epb.
*
* In Produktion wuerde hier CppUTest oder Google Test stehen
* (siehe docs/SWE-Plan.docx, Abschnitt 7). Fuer die Demo
* bleibt das Framework klein, damit es ohne externe Abhaengigkeiten baut.
*/
#ifndef UNIT_TEST_FRAMEWORK_H
#define UNIT_TEST_FRAMEWORK_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int tests_run;
int tests_failed;
} TestStats;
extern TestStats g_test_stats;
#define TEST_BEGIN(name) \
do { \
++g_test_stats.tests_run; \
const char* _test_name = name; \
int _test_failed = 0; \
printf(" TEST %s ... ", _test_name);
#define TEST_END() \
if (_test_failed) { ++g_test_stats.tests_failed; \
printf("FAIL\n"); } \
else { printf("ok\n"); } \
} while (0)
#define ASSERT_TRUE(expr) \
do { if (!(expr)) { _test_failed = 1; \
printf("\n ASSERT_TRUE failed: %s (%s:%d)\n", \
#expr, __FILE__, __LINE__); } } while (0)
#define ASSERT_EQ(a, b) \
do { long long _a = (long long)(a), _b = (long long)(b); \
if (_a != _b) { _test_failed = 1; \
printf("\n ASSERT_EQ failed: %s=%lld != %s=%lld (%s:%d)\n", \
#a, _a, #b, _b, __FILE__, __LINE__); } \
} while (0)
#define ASSERT_NE(a, b) \
do { long long _a = (long long)(a), _b = (long long)(b); \
if (_a == _b) { _test_failed = 1; \
printf("\n ASSERT_NE failed: %s==%s==%lld (%s:%d)\n", \
#a, #b, _a, __FILE__, __LINE__); } \
} while (0)
#define TEST_SUMMARY() \
do { \
printf("\n%d tests run, %d failed.\n", \
g_test_stats.tests_run, g_test_stats.tests_failed); \
return (g_test_stats.tests_failed == 0) ? 0 : 1; \
} while (0)
#endif /* UNIT_TEST_FRAMEWORK_H */
+808
View File
@@ -0,0 +1,808 @@
#!/usr/bin/env python3
"""
Generate Doorstop-compatible Markdown items for the EPB demo.
Source of truth: the dict EPB_DATA below.
Outputs to: reqs/sys/, reqs/swe/, arch/sys/, arch/swe/
Each output file uses Doorstop's Markdown mode (YAML frontmatter + body).
Run: python3 tools/generate_doorstop_items.py
"""
from __future__ import annotations
import textwrap
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
# ---------------------------------------------------------------------------
# System Requirements
# ---------------------------------------------------------------------------
SYS_REQS = [
{
"id": "SYS-001", "asil": "D",
"title": "Halten der Parkbremse im Stillstand",
"text": (
"Wenn die Parkbremse aktiviert ist und das Fahrzeug stillsteht, "
"muss das EPB-System die mechanische Klemmkraft an beiden hinteren "
"Bremssaetteln aufrecht erhalten, bis ein Loesen ausdruecklich "
"angefordert wird. Sicherheitsziel: SG-01.\n\n"
"**Verifikation:** SiL-Test mit Auf-/Ab-Hangelung, Klemmkraftmessung."
),
},
{
"id": "SYS-002", "asil": "D",
"title": "Apply auf Fahrer-Anforderung",
"text": (
"Bei Betaetigung des EPB-Schalters in Apply-Richtung muss das "
"System innerhalb von 800 ms die Parkbremse anlegen, sofern die "
"Voraussetzungen erfuellt sind (Stillstand oder Geschwindigkeit "
"unter 5 km/h). Sicherheitsziel: SG-01."
),
},
{
"id": "SYS-003", "asil": "B",
"title": "Release auf Fahrer-Anforderung",
"text": (
"Bei Betaetigung des EPB-Schalters in Release-Richtung muss das "
"System die Parkbremse loesen, sofern die folgenden Voraussetzungen "
"erfuellt sind: Motor laeuft, Fahrer betaetigt Bremspedal, Gang "
"ist eingelegt. Maximalzeit fuer Loesen: 1500 ms."
),
},
{
"id": "SYS-004", "asil": "D",
"title": "Auto-Apply bei Motor-Aus",
"text": (
"Wenn der Motor ausgeschaltet wird und das Fahrzeug stillsteht "
"und keine Parkbremse aktiv ist, muss das System die Parkbremse "
"spaetestens 2 s nach Erkennung Motor-Aus automatisch anlegen. "
"Sicherheitsziel: SG-01."
),
},
{
"id": "SYS-005", "asil": "D",
"title": "Hill-Hold am Berg",
"text": (
"Bei aktivem Hill-Hold (Fahrzeug steht am Hang mit Neigung > 5%, "
"Fahrer betaetigt Bremspedal) uebernimmt das EPB-System die "
"Bremskraft beim Loesen des Bremspedals und haelt diese, bis die "
"Anfahrt erkannt wird. Sicherheitsziel: SG-01."
),
},
{
"id": "SYS-006", "asil": "B",
"title": "Auto-Release beim Anfahren (Drive-Away-Assist)",
"text": (
"Wenn die Parkbremse aktiv ist und der Fahrer Anfahrabsicht zeigt "
"(Gaspedal-Betaetigung bei eingelegtem Gang), muss das System die "
"Parkbremse innerhalb von 500 ms loesen. Voraussetzung: alle "
"Sicherheitskriterien (Fahrertuer geschlossen, Sicherheitsgurt) "
"erfuellt."
),
},
{
"id": "SYS-007", "asil": "B",
"title": "Aktor-Stromueberwachung",
"text": (
"Das System muss den Motorstrom jedes Aktors mit mindestens 1 kHz "
"ueberwachen und bei Ueberschreitung von 8 A fuer mehr als 100 ms "
"den Aktor abschalten und einen DTC setzen. Sicherheitsziel: SG-03."
),
},
{
"id": "SYS-008", "asil": "QM",
"title": "Service-Modus fuer Werkstatt",
"text": (
"Das System muss ueber UDS RoutineControl (Service 0x31) einen "
"Service-Modus bereitstellen, in dem die Aktoren manuell in "
"Wartungs-Position gefahren werden koennen (z.B. fuer Bremsbelag-"
"Wechsel)."
),
},
{
"id": "SYS-009", "asil": "QM",
"title": "UDS-Diagnose",
"text": (
"Das System muss UDS-Diagnose nach ISO 14229 bereitstellen: "
"ReadDTC (0x19), ReadDataByIdentifier (0x22), RoutineControl (0x31), "
"ECUReset (0x11). Tester-Adresse 0x712, Antwort-Adresse 0x71A."
),
},
{
"id": "SYS-010", "asil": "QM",
"title": "HMI-Statusanzeige",
"text": (
"Der EPB-Status muss dem Fahrer signalisiert werden: LED am "
"Schalter (an = Apply, aus = Release, blinkend = Fehler) sowie "
"Text im Kombi-Display via CAN-Bus (Frame-ID 0x3A0, 50 Hz)."
),
},
]
# ---------------------------------------------------------------------------
# Software Requirements (each links to one or more SYS reqs)
# ---------------------------------------------------------------------------
SWE_REQS = [
# SYS-001 — Halten
{"id": "SWE-001", "asil": "D", "links": ["SYS-001"],
"title": "Apply-Controller haelt Klemmkraft",
"text": "Der Apply-Controller muss die Klemmkraft im Hold-Zustand alle 50 ms "
"verifizieren und bei Abweichung > 10% nachregeln."},
{"id": "SWE-002", "asil": "D", "links": ["SYS-001"],
"title": "Watchdog ueberwacht Apply-Controller",
"text": "Ein unabhaengiger Watchdog muss die Liveness des Apply-Controllers "
"mit 100 ms Timeout ueberwachen und bei Ausbleiben in den sicheren "
"Zustand (Apply) gehen."},
# SYS-002 — Apply
{"id": "SWE-003", "asil": "D", "links": ["SYS-002"],
"title": "Schalter-Apply-Signal an Apply-Controller weiterleiten",
"text": "Das Software-Modul Switch-Debouncer muss ein entprelltes "
"Apply-Signal innerhalb von 50 ms an den Apply-Controller liefern."},
{"id": "SWE-004", "asil": "D", "links": ["SYS-002"],
"title": "Klemmkraft-Erreichen bestaetigen",
"text": "Der Apply-Controller muss das Erreichen der Ziel-Klemmkraft via "
"Strommessung erkennen und ein Status-Flag setzen."},
# SYS-003 — Release
{"id": "SWE-005", "asil": "B", "links": ["SYS-003"],
"title": "Release-Voraussetzungen pruefen",
"text": "Vor jedem Release muss der Apply-Controller pruefen: Motor laeuft, "
"Bremspedal betaetigt, Gang ist eingelegt. Andernfalls Release abweisen."},
{"id": "SWE-006", "asil": "B", "links": ["SYS-003"],
"title": "Aktoren in Release-Position fahren",
"text": "Der Actuator-Driver muss beide Aktoren parallel in Release-Position "
"fahren. Maximalzeit: 1200 ms. Bei Timeout DTC setzen."},
# SYS-004 — Auto-Apply
{"id": "SWE-007", "asil": "D", "links": ["SYS-004"],
"title": "Motor-Aus-Bedingung erkennen",
"text": "Der Safety-Manager muss erkennen: Motor-Status = aus, "
"Geschwindigkeit < 0.5 km/h. Auswertezyklus 50 ms."},
{"id": "SWE-008", "asil": "D", "links": ["SYS-004"],
"title": "Auto-Apply nach 2 s Verzoegerung",
"text": "Ist die Motor-Aus-Bedingung 2 s stabil erfuellt und Parkbremse "
"noch nicht aktiv, muss der Safety-Manager Apply-Anforderung an "
"den Apply-Controller senden."},
# SYS-005 — Hill-Hold
{"id": "SWE-009", "asil": "D", "links": ["SYS-005"],
"title": "Hill-Hold-Aktivierungsbedingung",
"text": "Der Safety-Manager muss Hill-Hold aktivieren, wenn Neigung "
"(gefiltert) > 5%, Geschwindigkeit < 0.5 km/h und Bremspedal "
"betaetigt sind."},
{"id": "SWE-010", "asil": "D", "links": ["SYS-005"],
"title": "Hill-Hold-Uebergabe an Apply-Controller",
"text": "Wird das Bremspedal bei aktivem Hill-Hold losgelassen, muss der "
"Safety-Manager unmittelbar Apply-Anforderung an den Apply-"
"Controller senden, bevor das Fahrzeug zu rollen beginnen kann."},
# SYS-006 — Auto-Release
{"id": "SWE-011", "asil": "B", "links": ["SYS-006"],
"title": "Anfahrabsicht erkennen",
"text": "Anfahrabsicht ist erkannt, wenn: Gaspedal > 10%, Gang in Vorwaerts "
"oder Rueckwaerts, Motor laeuft."},
{"id": "SWE-012", "asil": "B", "links": ["SYS-006"],
"title": "Sicherheits-Check vor Auto-Release",
"text": "Vor Auto-Release muessen erfuellt sein: Fahrertuer geschlossen, "
"Sicherheitsgurt angelegt. Andernfalls warnen und nicht loesen."},
# SYS-007 — Aktor-Strom
{"id": "SWE-013", "asil": "B", "links": ["SYS-007"],
"title": "Strommessung mit 1 kHz",
"text": "Der Actuator-Driver muss den Motorstrom jedes Aktors mit "
"mindestens 1 kHz abtasten. Genauigkeit +/- 100 mA."},
{"id": "SWE-014", "asil": "B", "links": ["SYS-007"],
"title": "Overcurrent-Cutoff",
"text": "Bei Motorstrom > 8 A laenger als 100 ms muss der Actuator-Driver "
"den Motor abschalten und einen DTC P0xxx setzen."},
{"id": "SWE-015", "asil": "B", "links": ["SYS-007"],
"title": "Klemmkraft-Schaetzung aus Strom-Profil",
"text": "Der Actuator-Driver muss die erreichte Klemmkraft aus dem "
"Stromverlauf bei Apply schaetzen (Modell: F = k * I_peak)."},
# SYS-008 — Service-Modus
{"id": "SWE-016", "asil": "QM", "links": ["SYS-008"],
"title": "UDS RoutineControl 0x31 fuer Service-Release",
"text": "Service-Mode wird ueber UDS RoutineControl Service 0x31, "
"Routine-ID 0x0301 aktiviert. Bedingung: Fahrzeug muss stillstehen."},
{"id": "SWE-017", "asil": "QM", "links": ["SYS-008"],
"title": "Service-Mode-Indikator",
"text": "Im Service-Mode muss die EPB-LED am Schalter mit 2 Hz blinken."},
# SYS-009 — UDS
{"id": "SWE-018", "asil": "QM", "links": ["SYS-009"],
"title": "UDS Service 0x19 ReadDTC",
"text": "Das System muss alle gespeicherten DTCs ueber Service 0x19 "
"(Subfunktion 0x02 reportDTCByStatusMask) ausgeben."},
{"id": "SWE-019", "asil": "QM", "links": ["SYS-009"],
"title": "UDS Service 0x22 ReadDataByIdentifier",
"text": "Folgende DIDs muessen lesbar sein: 0xF187 (SW-Version), "
"0x0301 (Klemmkraft links), 0x0302 (Klemmkraft rechts)."},
# SYS-010 — HMI
{"id": "SWE-020", "asil": "QM", "links": ["SYS-010"],
"title": "LED-Steuerung",
"text": "Apply-aktiv: LED dauerleuchtend. Release: LED aus. Fehler: "
"LED blinkt 4 Hz. Service-Mode: LED blinkt 2 Hz."},
{"id": "SWE-021", "asil": "QM", "links": ["SYS-010"],
"title": "CAN-Status-Frame",
"text": "Status-Frame 0x3A0 mit 50 Hz: Byte 0 = Status (0=Released, 1=Applied, "
"2=Applying, 3=Releasing, 0xFF=Error), Byte 1-2 = Klemmkraft links, "
"Byte 3-4 = Klemmkraft rechts."},
# Sensorik & Plausibilisierung
{"id": "SWE-022", "asil": "B", "links": ["SYS-001", "SYS-002", "SYS-006"],
"title": "Stillstands-Erkennung aus Wheel Speeds",
"text": "Stillstand ist erkannt, wenn alle 4 Wheel-Speed-Signale fuer "
"mindestens 200 ms unter 0.5 km/h liegen."},
{"id": "SWE-023", "asil": "B", "links": ["SYS-007"],
"title": "Wheel Speed Plausibilisierung",
"text": "Spreizung der Wheel-Speed-Signale: bei Geradeaus-Fahrt darf die "
"Differenz nicht > 3 km/h sein. Andernfalls Sensor-Fehler-DTC."},
{"id": "SWE-024", "asil": "B", "links": ["SYS-005"],
"title": "Inclinometer Tiefpass-Filter",
"text": "Das Roh-Neigungssignal muss mit einem Tiefpass 1. Ordnung "
"(Zeitkonstante 200 ms) gefiltert werden, bevor es zur Hill-Hold-"
"Bewertung verwendet wird."},
{"id": "SWE-025", "asil": "QM", "links": ["SYS-002", "SYS-003"],
"title": "Switch-Debouncing",
"text": "Der EPB-Schalter muss mit einer Entprell-Zeit von 50 ms "
"entprellt werden. Stabiler Pegel = Eingangssignal fuer "
"Apply-Controller."},
]
# ---------------------------------------------------------------------------
# System Architecture Elements
# ---------------------------------------------------------------------------
SA_ELEMENTS = [
{
"id": "SA-001", "links": ["SYS-001", "SYS-002", "SYS-003", "SYS-004",
"SYS-005", "SYS-006", "SYS-007", "SYS-008",
"SYS-009", "SYS-010"],
"title": "EPB ECU",
"asil": "D",
"text": textwrap.dedent("""
## Verantwortung
Zentrales Steuergeraet der elektrischen Parkbremse. Beinhaltet alle Software-
Komponenten und die elektronische Ansteuerung der Aktoren.
## System-Kontext
```plantuml
@startuml
node "EPB ECU" as ECU
node "Aktor links" as AL
node "Aktor rechts" as AR
node "Wheel Speed Sensoren (x4)" as WS
node "Inclinometer" as IN
node "EPB-Schalter + LED" as SW
node "CAN-Bus" as CAN
node "Kombi-Display" as DI
node "OBD-Tester" as OBD
ECU --> AL : PWM, I-Mess
ECU --> AR : PWM, I-Mess
WS --> ECU : Pulse
IN --> ECU : SPI
SW --> ECU : GPIO
ECU --> SW : LED
ECU <-> CAN
CAN <-> DI
CAN <-> OBD
@enduml
```
## Schnittstellen
| Schnittstelle | Typ | Richtung |
|---------------|----------------|----------|
| Aktor L/R | PWM + Shunt | I/O |
| Wheel Speed | Hall-Pulse | In |
| Inclinometer | SPI | In |
| Schalter | GPIO debounced | In |
| LED | GPIO | Out |
| CAN | ISO 11898 | I/O |
## Subkomponenten (Aufteilung auf SW)
Realisiert in Software: alle SWA-Elemente SWA-001..SWA-010.
## Nichtfunktionale Eigenschaften
- Worst-Case Reaktionszeit (Schalter → Aktor-Bewegung): 250 ms
- Flash-Bedarf: < 256 KB
- RAM-Bedarf: < 32 KB
- Stromaufnahme: < 200 mA (Standby) / < 30 A (Aktor-Spitze)
""").strip(),
},
{
"id": "SA-002", "links": ["SYS-001", "SYS-002", "SYS-003", "SYS-007"],
"title": "Aktoren (Caliper-Motoren)",
"asil": "D",
"text": textwrap.dedent("""
## Verantwortung
Zwei elektromechanische Aktoren an den hinteren Bremssaetteln klemmen
und loesen die Bremsbelaege. Geliefert (Annahme): kommerzielles Bauteil
eines Tier-1-Lieferanten.
## Schnittstellen
| Schnittstelle | Typ | Bemerkung |
|---------------|--------------|-----------------------------------|
| Power | 12 V, PWM | bidirektional fuer Apply/Release |
| Strom-Shunt | Analog | wird in der ECU abgegriffen |
## Nichtfunktionale Eigenschaften
- Max. Klemmkraft: 20 kN
- Apply-Zeit (0 → max): 600 ms
- Strom (nominal): 4 A
- Strom (Spitze): 30 A (kurzzeitig)
- Temperaturbereich: -40°C bis +85°C
""").strip(),
},
{
"id": "SA-003", "links": ["SYS-005", "SYS-006", "SYS-007"],
"title": "Sensor-Cluster",
"asil": "B",
"text": textwrap.dedent("""
## Verantwortung
Zusammenfassung aller fuer die EPB benoetigten Eingangssignale:
Wheel-Speed-Sensoren (4x), Inclinometer (1x), EPB-Schalter, Bremspedal-
Status, Gear-Position, Door-Open, Seat-Belt — die letzten vier per CAN.
## Schnittstellen
| Sensor | Typ | Quelle |
|-----------------|------------------|--------------|
| Wheel Speed x4 | Hall-Pulse | direkt |
| Inclinometer | SPI 1 kHz | direkt |
| EPB-Schalter | GPIO | direkt |
| Bremspedal | CAN 0x100 | aus BCM |
| Gear | CAN 0x110 | aus TCU |
| Door / Belt | CAN 0x120 | aus BCM |
## Nichtfunktionale Eigenschaften
- Wheel-Speed-Genauigkeit: +/- 0.1 km/h ab 1 km/h
- Inclinometer-Genauigkeit: +/- 0.5°
- Sampling-Frequenz Inclinometer: 100 Hz
""").strip(),
},
{
"id": "SA-004", "links": ["SYS-008", "SYS-010"],
"title": "HMI (Schalter, LED, Display)",
"asil": "QM",
"text": textwrap.dedent("""
## Verantwortung
Fahrer-Interaktion und -Information: Tippschalter mit integrierter LED,
Statusanzeige im Kombi-Display via CAN.
## Schnittstellen
| Element | Typ | Verhalten |
|---------------|----------|--------------------------------------------|
| Tippschalter | GPIO | Apply-Richtung / Release-Richtung |
| LED | GPIO | aus / an / blink 2 Hz / blink 4 Hz |
| Display | CAN 0x3A0 | 50 Hz Status-Frame |
""").strip(),
},
{
"id": "SA-005", "links": ["SYS-009", "SYS-010"],
"title": "CAN-Bus",
"asil": "QM",
"text": textwrap.dedent("""
## Verantwortung
Kommunikations-Backbone fuer Eingangsdaten (Bremspedal, Gang, Tuer, Gurt),
Ausgabe (Status-Frame an Display) und Diagnose (UDS auf Tester-Adresse).
## Schnittstellen
- Baudrate: 500 kbit/s, CAN 2.0B
- Empfangene Frames: 0x100 (Bremspedal), 0x110 (Gang), 0x120 (Door/Belt),
0x712 (UDS-Request)
- Gesendete Frames: 0x3A0 (Status 50 Hz), 0x71A (UDS-Response)
""").strip(),
},
]
# ---------------------------------------------------------------------------
# Software Architecture Elements
# ---------------------------------------------------------------------------
SWA_ELEMENTS = [
{
"id": "SWA-001", "asil": "D",
"links": ["SWE-007", "SWE-008", "SWE-009", "SWE-010"],
"title": "Safety Manager",
"text": textwrap.dedent("""
## Verantwortung
Hoechste Sicherheitsschicht. Erkennt Motor-Aus, aktiviert Hill-Hold,
triggert Auto-Apply. Lebenswichtige Logik mit redundanter Pruefung.
## Statische Sicht
```plantuml
@startuml
package "Safety Manager" {
[Engine State Monitor]
[Hill-Hold Logic]
[Auto-Apply Logic]
}
[Safety Manager] ..> [Apply Controller] : Apply-Anforderung
[Wheel Speed Plausi] --> [Safety Manager] : v_vehicle
[Inclinometer Filter] --> [Safety Manager] : grade
@enduml
```
## Schnittstellen (Provided)
```c
Status safety_mgr_init(void);
void safety_mgr_step_50ms(const SafetyInputs* in);
```
## Dynamisches Verhalten
```plantuml
@startuml
[*] --> Idle
Idle --> HillHoldArmed : grade>5% & v=0 & brake
HillHoldArmed --> HillHoldActive : brake released
HillHoldActive --> Idle : v>2 km/h
Idle --> AutoApplyArmed : engine_off & v=0
AutoApplyArmed --> AutoApplyTriggered : t>=2s
AutoApplyTriggered --> Idle : applied
@enduml
```
## Ressourcen
- Stack: <= 256 B
- Worst-Case Timing: 200 us / Aufruf
## Mapping auf Anforderungen
| Anforderung | Wie abgedeckt |
|-------------|---------------|
| SWE-007 | engine_off + v<0.5 in step_50ms |
| SWE-008 | 2s-Filter und Trigger |
| SWE-009 | Hill-Hold-Aktivierung |
| SWE-010 | Brake-Released-Detektion |
""").strip(),
},
{
"id": "SWA-002", "asil": "D",
"links": ["SWE-001", "SWE-002", "SWE-003", "SWE-004"],
"title": "Apply Controller",
"text": textwrap.dedent("""
## Verantwortung
Zentraler Controller fuer Apply, Hold und Release der Parkbremse.
ASIL-D-Kern der EPB-Software. Implementiert in `src/apply_controller.c`.
## Statische Sicht
```plantuml
@startuml
[Apply Controller] --> [Actuator Driver L] : apply/release
[Apply Controller] --> [Actuator Driver R] : apply/release
[Switch Debouncer] --> [Apply Controller] : sw_apply, sw_release
[Safety Manager] --> [Apply Controller] : auto_apply, hill_hold_request
[Apply Controller] --> [Display Manager] : status
[Apply Controller] <-- [Watchdog] : alive_check
@enduml
```
## Schnittstellen (Provided)
```c
Status apply_ctrl_init(void);
void apply_ctrl_step_50ms(const ApplyInputs* in);
EpbStatus apply_ctrl_get_status(void);
```
## Dynamisches Verhalten
```plantuml
@startuml
[*] --> Released
Released --> Applying : apply_request & v_low
Applying --> Applied : current_target_reached
Applied --> Releasing : release_request & preconditions_ok
Applied --> Applied : 50ms hold check (re-clamp if needed)
Releasing --> Released : release_complete
Applying --> Error : timeout > 1500ms
Releasing --> Error : timeout > 1200ms
Error --> Released : reset & no fault
@enduml
```
## Ressourcen
- Stack: <= 384 B
- Worst-Case Timing: 350 us / Aufruf
## Designentscheidungen
| Entscheidung | Begruendung |
|--------------|-------------|
| Statische Allokation, kein Heap | Determinismus, MISRA C 21.3 |
| State Machine | Einfacher zu verifizieren, deterministisch |
| 50ms Step-Funktion | Synchron zur Inclinometer-Abtastung |
## Mapping auf Anforderungen
| Anforderung | Wie abgedeckt |
|-------------|---------------|
| SWE-001 | Hold-Zustand mit periodischer Klemmkraft-Pruefung |
| SWE-002 | Watchdog-Pet im step_50ms |
| SWE-003 | sw_apply Input wird sofort ausgewertet |
| SWE-004 | Current-Target-Detektion via Actuator-Driver-Feedback |
""").strip(),
},
{
"id": "SWA-003", "asil": "B",
"links": ["SWE-006", "SWE-013", "SWE-014", "SWE-015"],
"title": "Actuator Driver",
"text": textwrap.dedent("""
## Verantwortung
Low-Level-Ansteuerung der beiden Aktor-Motoren. PWM-Generierung,
Strom-Messung, Overcurrent-Cutoff, Klemmkraft-Schaetzung.
Implementiert in `src/actuator_driver.c`.
## Statische Sicht
```plantuml
@startuml
[Apply Controller] --> [Actuator Driver]
[Actuator Driver] --> [Hardware PWM] : pwm_set
[Actuator Driver] <-- [Hardware ADC] : current_sample
[Actuator Driver] --> [Diagnostic Manager] : DTC
@enduml
```
## Schnittstellen (Provided)
```c
Status actuator_init(void);
void actuator_apply(ActuatorId id, uint8_t pwm_percent);
void actuator_release(ActuatorId id, uint8_t pwm_percent);
void actuator_stop(ActuatorId id);
ActuatorStatus actuator_get_status(ActuatorId id);
void actuator_isr_1khz(void); // Strom-Sampling
```
## Ressourcen
- Stack: <= 256 B
- Worst-Case Timing: 50 us / ISR
- Static RAM: 64 B pro Aktor
## Mapping auf Anforderungen
| Anforderung | Wie abgedeckt |
|-------------|---------------|
| SWE-006 | actuator_release fuer beide Aktoren parallel |
| SWE-013 | actuator_isr_1khz |
| SWE-014 | Overcurrent-Detektor in ISR |
| SWE-015 | Peak-Current-Tracking + lineare Klemmkraft-Schaetzung |
""").strip(),
},
{
"id": "SWA-004", "asil": "B",
"links": ["SWE-022", "SWE-023"],
"title": "Wheel Speed Plausibilisierung",
"text": textwrap.dedent("""
## Verantwortung
Aufbereitung und Plausibilisierung der 4 Wheel-Speed-Signale. Erkennt
Stillstand und plausibilisiert untereinander.
## Schnittstellen (Provided)
```c
Status wheel_speed_init(void);
void wheel_speed_step_10ms(const WheelInputs* in);
bool wheel_speed_is_standstill(void);
float wheel_speed_get_vehicle(void);
```
""").strip(),
},
{
"id": "SWA-005", "asil": "B",
"links": ["SWE-024"],
"title": "Inclinometer Filter",
"text": textwrap.dedent("""
## Verantwortung
Tiefpass-Filterung des Inclinometer-Roh-Signals fuer die Hill-Hold-Bewertung.
## Schnittstellen (Provided)
```c
Status inclino_init(void);
void inclino_step_10ms(int16_t raw_mdeg);
float inclino_get_grade_percent(void);
```
""").strip(),
},
{
"id": "SWA-006", "asil": "QM",
"links": ["SWE-025"],
"title": "Switch Debouncer",
"text": textwrap.dedent("""
## Verantwortung
Software-Entprellung des EPB-Schalters. Liefert stabiles Apply / Release
Signal an den Apply-Controller. Implementiert in `src/switch_debouncer.c`.
## Schnittstellen (Provided)
```c
Status switch_init(void);
void switch_step_10ms(SwitchRaw raw);
SwitchState switch_get_state(void);
```
## Mapping auf Anforderungen
| Anforderung | Wie abgedeckt |
|-------------|---------------|
| SWE-025 | 50ms Debounce-Logik |
""").strip(),
},
{
"id": "SWA-007", "asil": "QM",
"links": ["SWE-020", "SWE-021"],
"title": "Display Manager",
"text": textwrap.dedent("""
## Verantwortung
Steuert LED am EPB-Schalter und CAN-Status-Frame an das Kombi-Display.
Empfaengt Status vom Apply-Controller.
## Schnittstellen (Provided)
```c
Status display_init(void);
void display_set_status(EpbStatus s);
void display_step_20ms(void); // 50 Hz CAN-Frame
```
""").strip(),
},
{
"id": "SWA-008", "asil": "QM",
"links": ["SWE-018", "SWE-019"],
"title": "Diagnostic Manager",
"text": textwrap.dedent("""
## Verantwortung
UDS-Diagnose nach ISO 14229: ReadDTC, ReadDataByIdentifier, RoutineControl.
## Schnittstellen (Provided)
```c
Status diag_init(void);
void diag_handle_request(const uint8_t* data, uint16_t len);
void diag_set_dtc(uint16_t dtc_id);
```
""").strip(),
},
{
"id": "SWA-009", "asil": "QM",
"links": ["SWE-016", "SWE-017"],
"title": "Service Mode",
"text": textwrap.dedent("""
## Verantwortung
Service-Modus fuer Werkstatt. Wird ueber UDS RoutineControl 0x31, Routine-ID
0x0301 aktiviert. Steuert Aktoren in Wartungsposition.
""").strip(),
},
{
"id": "SWA-010", "asil": "QM",
"links": ["SWE-018", "SWE-019"],
"title": "Logger",
"text": textwrap.dedent("""
## Verantwortung
Logging fuer Entwicklung und Service. Ringpuffer im RAM (1 KB) sowie
Persistenz im EEPROM bei kritischen Ereignissen.
## Schnittstellen (Provided)
```c
Status log_init(void);
void log_event(LogLevel lvl, uint16_t event_id, uint32_t param);
```
""").strip(),
},
]
# ---------------------------------------------------------------------------
# Generation
# ---------------------------------------------------------------------------
REQ_FRONTMATTER = textwrap.dedent("""
---
active: true
derived: false
header: '{title}'
level: 1.{level}
normative: true
reviewed: null
links:{links_yaml}
asil: {asil}
---
# {id}: {title}
{text}
""").strip() + "\n"
def emit_links(links):
if not links:
return " []"
parts = ["\n - {}".format(l) for l in links]
return "".join(parts)
def write_items(items, target_dir: Path, with_links=True):
target_dir.mkdir(parents=True, exist_ok=True)
for i, item in enumerate(items, start=1):
path = target_dir / f"{item['id']}.md"
links_yaml = emit_links(item.get("links", []))
content = REQ_FRONTMATTER.format(
title=item["title"],
level=i,
links_yaml=links_yaml,
asil=item["asil"],
id=item["id"],
text=item["text"],
)
path.write_text(content)
print(f"Wrote {len(items)} items to {target_dir.relative_to(REPO)}/")
def main():
write_items(SYS_REQS, REPO / "reqs" / "sys")
write_items(SWE_REQS, REPO / "reqs" / "swe")
write_items(SA_ELEMENTS, REPO / "arch" / "sys")
write_items(SWA_ELEMENTS, REPO / "arch" / "swe")
print("\nTotal: {} reqs/arch items.".format(
len(SYS_REQS) + len(SWE_REQS) + len(SA_ELEMENTS) + len(SWA_ELEMENTS)
))
if __name__ == "__main__":
main()