commit 1855162e6d783d2aebdbabc7acb62883fdedee8f Author: Stefan Lohmaier Date: Mon May 11 13:51:02 2026 -0700 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 diff --git a/.doorstop.yml b/.doorstop.yml new file mode 100644 index 0000000..fecac25 --- /dev/null +++ b/.doorstop.yml @@ -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 diff --git a/.gitea/workflows/validate.yml b/.gitea/workflows/validate.yml new file mode 100644 index 0000000..ff0c798 --- /dev/null +++ b/.gitea/workflows/validate.yml @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eacaa1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +build/ +*.o +*.gcno +*.gcda +*.gcov + +# macOS +.DS_Store + +# Editor +.vscode/ +.idea/ +*.swp +*~ + +# Python +__pycache__/ +*.pyc +.venv/ + +# Generated artefacts +*.pdf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4686cfe --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Stefan Lohmaier + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..75482fd --- /dev/null +++ b/Makefile @@ -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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..3208383 --- /dev/null +++ b/README.md @@ -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). diff --git a/arch/swe/SWA-001.md b/arch/swe/SWA-001.md new file mode 100644 index 0000000..2b0c773 --- /dev/null +++ b/arch/swe/SWA-001.md @@ -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 | diff --git a/arch/swe/SWA-002.md b/arch/swe/SWA-002.md new file mode 100644 index 0000000..35ab07d --- /dev/null +++ b/arch/swe/SWA-002.md @@ -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 | diff --git a/arch/swe/SWA-003.md b/arch/swe/SWA-003.md new file mode 100644 index 0000000..b73b426 --- /dev/null +++ b/arch/swe/SWA-003.md @@ -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 | diff --git a/arch/swe/SWA-004.md b/arch/swe/SWA-004.md new file mode 100644 index 0000000..9c9edac --- /dev/null +++ b/arch/swe/SWA-004.md @@ -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); +``` diff --git a/arch/swe/SWA-005.md b/arch/swe/SWA-005.md new file mode 100644 index 0000000..c0f5c21 --- /dev/null +++ b/arch/swe/SWA-005.md @@ -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); +``` diff --git a/arch/swe/SWA-006.md b/arch/swe/SWA-006.md new file mode 100644 index 0000000..589a10c --- /dev/null +++ b/arch/swe/SWA-006.md @@ -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 | diff --git a/arch/swe/SWA-007.md b/arch/swe/SWA-007.md new file mode 100644 index 0000000..649402f --- /dev/null +++ b/arch/swe/SWA-007.md @@ -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 +``` diff --git a/arch/swe/SWA-008.md b/arch/swe/SWA-008.md new file mode 100644 index 0000000..081e834 --- /dev/null +++ b/arch/swe/SWA-008.md @@ -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); +``` diff --git a/arch/swe/SWA-009.md b/arch/swe/SWA-009.md new file mode 100644 index 0000000..3f186d2 --- /dev/null +++ b/arch/swe/SWA-009.md @@ -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. diff --git a/arch/swe/SWA-010.md b/arch/swe/SWA-010.md new file mode 100644 index 0000000..0b7f56e --- /dev/null +++ b/arch/swe/SWA-010.md @@ -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); +``` diff --git a/arch/sys/SA-001.md b/arch/sys/SA-001.md new file mode 100644 index 0000000..07c032e --- /dev/null +++ b/arch/sys/SA-001.md @@ -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) diff --git a/arch/sys/SA-002.md b/arch/sys/SA-002.md new file mode 100644 index 0000000..bfa7390 --- /dev/null +++ b/arch/sys/SA-002.md @@ -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 diff --git a/arch/sys/SA-003.md b/arch/sys/SA-003.md new file mode 100644 index 0000000..b98b8f2 --- /dev/null +++ b/arch/sys/SA-003.md @@ -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 diff --git a/arch/sys/SA-004.md b/arch/sys/SA-004.md new file mode 100644 index 0000000..3efd47e --- /dev/null +++ b/arch/sys/SA-004.md @@ -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 | diff --git a/arch/sys/SA-005.md b/arch/sys/SA-005.md new file mode 100644 index 0000000..d96be79 --- /dev/null +++ b/arch/sys/SA-005.md @@ -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) diff --git a/docs/PID.docx b/docs/PID.docx new file mode 100644 index 0000000..b755f31 Binary files /dev/null and b/docs/PID.docx differ diff --git a/docs/PM-Plan.docx b/docs/PM-Plan.docx new file mode 100644 index 0000000..4f36b92 Binary files /dev/null and b/docs/PM-Plan.docx differ diff --git a/docs/QA-Plan.docx b/docs/QA-Plan.docx new file mode 100644 index 0000000..c32dd79 Binary files /dev/null and b/docs/QA-Plan.docx differ diff --git a/docs/SWE-Plan.docx b/docs/SWE-Plan.docx new file mode 100644 index 0000000..6c29473 Binary files /dev/null and b/docs/SWE-Plan.docx differ diff --git a/docs/Test-Plan.docx b/docs/Test-Plan.docx new file mode 100644 index 0000000..43d61f8 Binary files /dev/null and b/docs/Test-Plan.docx differ diff --git a/docs/non-conformities-md/NC-001.md b/docs/non-conformities-md/NC-001.md new file mode 100644 index 0000000..2881ecc --- /dev/null +++ b/docs/non-conformities-md/NC-001.md @@ -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 ``) +- 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. diff --git a/docs/non-conformities/NC-001.docx b/docs/non-conformities/NC-001.docx new file mode 100644 index 0000000..c36975f Binary files /dev/null and b/docs/non-conformities/NC-001.docx differ diff --git a/docs/plans-md/PID.md b/docs/plans-md/PID.md new file mode 100644 index 0000000..a5bfcfe --- /dev/null +++ b/docs/plans-md/PID.md @@ -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 diff --git a/docs/plans-md/PM-Plan.md b/docs/plans-md/PM-Plan.md new file mode 100644 index 0000000..0349314 --- /dev/null +++ b/docs/plans-md/PM-Plan.md @@ -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. diff --git a/docs/plans-md/QA-Plan.md b/docs/plans-md/QA-Plan.md new file mode 100644 index 0000000..fe94571 --- /dev/null +++ b/docs/plans-md/QA-Plan.md @@ -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. diff --git a/docs/plans-md/SWE-Plan.md b/docs/plans-md/SWE-Plan.md new file mode 100644 index 0000000..2bff111 --- /dev/null +++ b/docs/plans-md/SWE-Plan.md @@ -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. diff --git a/docs/plans-md/Test-Plan.md b/docs/plans-md/Test-Plan.md new file mode 100644 index 0000000..aa61abc --- /dev/null +++ b/docs/plans-md/Test-Plan.md @@ -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/` diff --git a/docs/reviews-md/Review-001.md b/docs/reviews-md/Review-001.md new file mode 100644 index 0000000..3f3b709 --- /dev/null +++ b/docs/reviews-md/Review-001.md @@ -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).* diff --git a/docs/reviews/REV-001.docx b/docs/reviews/REV-001.docx new file mode 100644 index 0000000..f29a454 Binary files /dev/null and b/docs/reviews/REV-001.docx differ diff --git a/misra/records-md/MISRA-Record-001.md b/misra/records-md/MISRA-Record-001.md new file mode 100644 index 0000000..6051f9f --- /dev/null +++ b/misra/records-md/MISRA-Record-001.md @@ -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. diff --git a/misra/records/MISRA-REC-001.docx b/misra/records/MISRA-REC-001.docx new file mode 100644 index 0000000..989b590 Binary files /dev/null and b/misra/records/MISRA-REC-001.docx differ diff --git a/reqs/swe/SWE-001.md b/reqs/swe/SWE-001.md new file mode 100644 index 0000000..5c64029 --- /dev/null +++ b/reqs/swe/SWE-001.md @@ -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. diff --git a/reqs/swe/SWE-002.md b/reqs/swe/SWE-002.md new file mode 100644 index 0000000..b9dbc19 --- /dev/null +++ b/reqs/swe/SWE-002.md @@ -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. diff --git a/reqs/swe/SWE-003.md b/reqs/swe/SWE-003.md new file mode 100644 index 0000000..c34a3fe --- /dev/null +++ b/reqs/swe/SWE-003.md @@ -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. diff --git a/reqs/swe/SWE-004.md b/reqs/swe/SWE-004.md new file mode 100644 index 0000000..8aa595b --- /dev/null +++ b/reqs/swe/SWE-004.md @@ -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. diff --git a/reqs/swe/SWE-005.md b/reqs/swe/SWE-005.md new file mode 100644 index 0000000..fc9012c --- /dev/null +++ b/reqs/swe/SWE-005.md @@ -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. diff --git a/reqs/swe/SWE-006.md b/reqs/swe/SWE-006.md new file mode 100644 index 0000000..1606fb6 --- /dev/null +++ b/reqs/swe/SWE-006.md @@ -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. diff --git a/reqs/swe/SWE-007.md b/reqs/swe/SWE-007.md new file mode 100644 index 0000000..e7f95c0 --- /dev/null +++ b/reqs/swe/SWE-007.md @@ -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. diff --git a/reqs/swe/SWE-008.md b/reqs/swe/SWE-008.md new file mode 100644 index 0000000..166e5fa --- /dev/null +++ b/reqs/swe/SWE-008.md @@ -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. diff --git a/reqs/swe/SWE-009.md b/reqs/swe/SWE-009.md new file mode 100644 index 0000000..bc4c335 --- /dev/null +++ b/reqs/swe/SWE-009.md @@ -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. diff --git a/reqs/swe/SWE-010.md b/reqs/swe/SWE-010.md new file mode 100644 index 0000000..d8669bd --- /dev/null +++ b/reqs/swe/SWE-010.md @@ -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. diff --git a/reqs/swe/SWE-011.md b/reqs/swe/SWE-011.md new file mode 100644 index 0000000..c5ea829 --- /dev/null +++ b/reqs/swe/SWE-011.md @@ -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. diff --git a/reqs/swe/SWE-012.md b/reqs/swe/SWE-012.md new file mode 100644 index 0000000..e1afc74 --- /dev/null +++ b/reqs/swe/SWE-012.md @@ -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. diff --git a/reqs/swe/SWE-013.md b/reqs/swe/SWE-013.md new file mode 100644 index 0000000..92b28fb --- /dev/null +++ b/reqs/swe/SWE-013.md @@ -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. diff --git a/reqs/swe/SWE-014.md b/reqs/swe/SWE-014.md new file mode 100644 index 0000000..148b75a --- /dev/null +++ b/reqs/swe/SWE-014.md @@ -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. diff --git a/reqs/swe/SWE-015.md b/reqs/swe/SWE-015.md new file mode 100644 index 0000000..8876a43 --- /dev/null +++ b/reqs/swe/SWE-015.md @@ -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). diff --git a/reqs/swe/SWE-016.md b/reqs/swe/SWE-016.md new file mode 100644 index 0000000..1e8345e --- /dev/null +++ b/reqs/swe/SWE-016.md @@ -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. diff --git a/reqs/swe/SWE-017.md b/reqs/swe/SWE-017.md new file mode 100644 index 0000000..7311f2b --- /dev/null +++ b/reqs/swe/SWE-017.md @@ -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. diff --git a/reqs/swe/SWE-018.md b/reqs/swe/SWE-018.md new file mode 100644 index 0000000..4277392 --- /dev/null +++ b/reqs/swe/SWE-018.md @@ -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. diff --git a/reqs/swe/SWE-019.md b/reqs/swe/SWE-019.md new file mode 100644 index 0000000..5787562 --- /dev/null +++ b/reqs/swe/SWE-019.md @@ -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). diff --git a/reqs/swe/SWE-020.md b/reqs/swe/SWE-020.md new file mode 100644 index 0000000..6221377 --- /dev/null +++ b/reqs/swe/SWE-020.md @@ -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. diff --git a/reqs/swe/SWE-021.md b/reqs/swe/SWE-021.md new file mode 100644 index 0000000..3546f3f --- /dev/null +++ b/reqs/swe/SWE-021.md @@ -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. diff --git a/reqs/swe/SWE-022.md b/reqs/swe/SWE-022.md new file mode 100644 index 0000000..428f424 --- /dev/null +++ b/reqs/swe/SWE-022.md @@ -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. diff --git a/reqs/swe/SWE-023.md b/reqs/swe/SWE-023.md new file mode 100644 index 0000000..acf1f48 --- /dev/null +++ b/reqs/swe/SWE-023.md @@ -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. diff --git a/reqs/swe/SWE-024.md b/reqs/swe/SWE-024.md new file mode 100644 index 0000000..a2b674d --- /dev/null +++ b/reqs/swe/SWE-024.md @@ -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. diff --git a/reqs/swe/SWE-025.md b/reqs/swe/SWE-025.md new file mode 100644 index 0000000..c1487dc --- /dev/null +++ b/reqs/swe/SWE-025.md @@ -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. diff --git a/reqs/sys/SYS-001.md b/reqs/sys/SYS-001.md new file mode 100644 index 0000000..5a2b5f3 --- /dev/null +++ b/reqs/sys/SYS-001.md @@ -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. diff --git a/reqs/sys/SYS-002.md b/reqs/sys/SYS-002.md new file mode 100644 index 0000000..c256486 --- /dev/null +++ b/reqs/sys/SYS-002.md @@ -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. diff --git a/reqs/sys/SYS-003.md b/reqs/sys/SYS-003.md new file mode 100644 index 0000000..9c2ff44 --- /dev/null +++ b/reqs/sys/SYS-003.md @@ -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. diff --git a/reqs/sys/SYS-004.md b/reqs/sys/SYS-004.md new file mode 100644 index 0000000..0c7ab76 --- /dev/null +++ b/reqs/sys/SYS-004.md @@ -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. diff --git a/reqs/sys/SYS-005.md b/reqs/sys/SYS-005.md new file mode 100644 index 0000000..f9c5933 --- /dev/null +++ b/reqs/sys/SYS-005.md @@ -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. diff --git a/reqs/sys/SYS-006.md b/reqs/sys/SYS-006.md new file mode 100644 index 0000000..b325dfc --- /dev/null +++ b/reqs/sys/SYS-006.md @@ -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. diff --git a/reqs/sys/SYS-007.md b/reqs/sys/SYS-007.md new file mode 100644 index 0000000..6e2cb6a --- /dev/null +++ b/reqs/sys/SYS-007.md @@ -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. diff --git a/reqs/sys/SYS-008.md b/reqs/sys/SYS-008.md new file mode 100644 index 0000000..a75665a --- /dev/null +++ b/reqs/sys/SYS-008.md @@ -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). diff --git a/reqs/sys/SYS-009.md b/reqs/sys/SYS-009.md new file mode 100644 index 0000000..61caff9 --- /dev/null +++ b/reqs/sys/SYS-009.md @@ -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. diff --git a/reqs/sys/SYS-010.md b/reqs/sys/SYS-010.md new file mode 100644 index 0000000..2e2baf9 --- /dev/null +++ b/reqs/sys/SYS-010.md @@ -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). diff --git a/src/actuator_driver.c b/src/actuator_driver.c new file mode 100644 index 0000000..91887cf --- /dev/null +++ b/src/actuator_driver.c @@ -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; + } +} diff --git a/src/actuator_driver.h b/src/actuator_driver.h new file mode 100644 index 0000000..bb7cae9 --- /dev/null +++ b/src/actuator_driver.h @@ -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 */ diff --git a/src/apply_controller.c b/src/apply_controller.c new file mode 100644 index 0000000..147099d --- /dev/null +++ b/src/apply_controller.c @@ -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 + +#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; +} diff --git a/src/apply_controller.h b/src/apply_controller.h new file mode 100644 index 0000000..3a708a2 --- /dev/null +++ b/src/apply_controller.h @@ -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 */ diff --git a/src/epb_types.h b/src/epb_types.h new file mode 100644 index 0000000..27a3a4a --- /dev/null +++ b/src/epb_types.h @@ -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 +#include + +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 */ diff --git a/src/stubs/README.md b/src/stubs/README.md new file mode 100644 index 0000000..3e61ede --- /dev/null +++ b/src/stubs/README.md @@ -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 | diff --git a/src/stubs/diag_manager.h b/src/stubs/diag_manager.h new file mode 100644 index 0000000..ed9a8a1 --- /dev/null +++ b/src/stubs/diag_manager.h @@ -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 diff --git a/src/stubs/display_manager.h b/src/stubs/display_manager.h new file mode 100644 index 0000000..b75ceea --- /dev/null +++ b/src/stubs/display_manager.h @@ -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 diff --git a/src/stubs/inclinometer.h b/src/stubs/inclinometer.h new file mode 100644 index 0000000..63c2945 --- /dev/null +++ b/src/stubs/inclinometer.h @@ -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 diff --git a/src/stubs/logger.h b/src/stubs/logger.h new file mode 100644 index 0000000..3e1a5e1 --- /dev/null +++ b/src/stubs/logger.h @@ -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 diff --git a/src/stubs/safety_manager.h b/src/stubs/safety_manager.h new file mode 100644 index 0000000..9db5241 --- /dev/null +++ b/src/stubs/safety_manager.h @@ -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 diff --git a/src/stubs/service_mode.h b/src/stubs/service_mode.h new file mode 100644 index 0000000..4ae7d10 --- /dev/null +++ b/src/stubs/service_mode.h @@ -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 diff --git a/src/stubs/wheel_speed_plausi.h b/src/stubs/wheel_speed_plausi.h new file mode 100644 index 0000000..c448206 --- /dev/null +++ b/src/stubs/wheel_speed_plausi.h @@ -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 diff --git a/src/switch_debouncer.c b/src/switch_debouncer.c new file mode 100644 index 0000000..3599896 --- /dev/null +++ b/src/switch_debouncer.c @@ -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; +} diff --git a/src/switch_debouncer.h b/src/switch_debouncer.h new file mode 100644 index 0000000..7c84afb --- /dev/null +++ b/src/switch_debouncer.h @@ -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 */ diff --git a/tests/unit/test_actuator_driver.c b/tests/unit/test_actuator_driver.c new file mode 100644 index 0000000..250f18f --- /dev/null +++ b/tests/unit/test_actuator_driver.c @@ -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(); +} diff --git a/tests/unit/test_apply_controller.c b/tests/unit/test_apply_controller.c new file mode 100644 index 0000000..9aa573b --- /dev/null +++ b/tests/unit/test_apply_controller.c @@ -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(); +} diff --git a/tests/unit/test_switch_debouncer.c b/tests/unit/test_switch_debouncer.c new file mode 100644 index 0000000..7c31e2c --- /dev/null +++ b/tests/unit/test_switch_debouncer.c @@ -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(); +} diff --git a/tests/unit_test_framework.h b/tests/unit_test_framework.h new file mode 100644 index 0000000..d66b836 --- /dev/null +++ b/tests/unit_test_framework.h @@ -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 +#include +#include + +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 */ diff --git a/tools/generate_doorstop_items.py b/tools/generate_doorstop_items.py new file mode 100644 index 0000000..ad30600 --- /dev/null +++ b/tools/generate_doorstop_items.py @@ -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()