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

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

Inhalte:
- 5 Plaene (PID, PM-, QA-, SWE-, Test-Plan) in Word, ausgefuellt mit
  EPB-spezifischen Inhalten
- 10 System-Anforderungen + 25 Software-Anforderungen (Doorstop-MD)
- 5 System-Architektur-Elemente + 10 Software-Architektur-Elemente
  mit PlantUML-Diagrammen und vollstaendigem Mapping
- 3 implementierte Komponenten (Apply Controller D, Actuator Driver B,
  Switch Debouncer QM) plus 7 Header-Stubs
- 28 Unit-Tests, alle gruen, mit Coverage- und MISRA-Build-Targets
- Audit-Artefakte: 1 Review-Protokoll, 1 Non-Conformity, 1 MISRA-Record
- Gitea-Actions-CI-Pipeline (validate.yml)
- Doorstop-Konfiguration fuer bidirektionale Traceability
- Generator-Skript fuer alle 50 Reqs/Arch-Elemente aus Strukturdaten
- README mit gefuehrter Tour fuer Prospects
This commit is contained in:
Stefan Lohmaier
2026-05-11 13:51:02 -07:00
commit 1855162e6d
92 changed files with 4116 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
/**
* @file actuator_driver.c
* @brief Implementierung der EPB-Aktor-Ansteuerung.
*
* @arch SWA-003
* @reqs SWE-006 SWE-013 SWE-014 SWE-015
*
* ASIL: B.
*/
#include "actuator_driver.h"
typedef struct {
ActuatorStatus status;
uint16_t over_ms; /* Millisekunden ueber Strom-Limit (zaehlt in 1 kHz ISR) */
} ActuatorCtx;
static ActuatorCtx s_ctx[ACTUATOR_COUNT];
static bool is_valid_id(ActuatorId id)
{
return (id == ACTUATOR_LEFT) || (id == ACTUATOR_RIGHT);
}
EpbStatus actuator_init(void)
{
for (uint8_t i = 0U; i < ACTUATOR_COUNT; ++i) {
s_ctx[i].status.direction = ACT_DIR_STOP;
s_ctx[i].status.pwm_percent = 0U;
s_ctx[i].status.current_ma = 0U;
s_ctx[i].status.peak_current_ma = 0U;
s_ctx[i].status.clamping_force_n = 0U;
s_ctx[i].status.overcurrent = false;
s_ctx[i].status.last_error = EPB_OK;
s_ctx[i].over_ms = 0U;
}
return EPB_OK;
}
EpbStatus actuator_apply(ActuatorId id, uint8_t pwm_percent)
{
if (!is_valid_id(id)) {
return EPB_EINVAL;
}
if (pwm_percent > 100U) {
return EPB_EINVAL;
}
if (s_ctx[id].status.overcurrent) {
return EPB_EOVERCURRENT;
}
s_ctx[id].status.direction = ACT_DIR_APPLY;
s_ctx[id].status.pwm_percent = pwm_percent;
s_ctx[id].status.peak_current_ma = 0U;
return EPB_OK;
}
EpbStatus actuator_release(ActuatorId id, uint8_t pwm_percent)
{
if (!is_valid_id(id)) {
return EPB_EINVAL;
}
if (pwm_percent > 100U) {
return EPB_EINVAL;
}
if (s_ctx[id].status.overcurrent) {
return EPB_EOVERCURRENT;
}
s_ctx[id].status.direction = ACT_DIR_RELEASE;
s_ctx[id].status.pwm_percent = pwm_percent;
return EPB_OK;
}
EpbStatus actuator_stop(ActuatorId id)
{
if (!is_valid_id(id)) {
return EPB_EINVAL;
}
s_ctx[id].status.direction = ACT_DIR_STOP;
s_ctx[id].status.pwm_percent = 0U;
return EPB_OK;
}
ActuatorStatus actuator_get_status(ActuatorId id)
{
if (!is_valid_id(id)) {
ActuatorStatus empty = {0};
empty.last_error = EPB_EINVAL;
return empty;
}
return s_ctx[id].status;
}
void actuator_isr_1khz(ActuatorId id, uint16_t current_sample_ma)
{
if (!is_valid_id(id)) {
return;
}
s_ctx[id].status.current_ma = current_sample_ma;
if (current_sample_ma > s_ctx[id].status.peak_current_ma) {
s_ctx[id].status.peak_current_ma = current_sample_ma;
}
/* SWE-014: Overcurrent-Cutoff bei > 8 A fuer > 100 ms */
if (current_sample_ma > ACT_OVERCURRENT_LIMIT_MA) {
if (s_ctx[id].over_ms < UINT16_MAX) {
++s_ctx[id].over_ms;
}
if (s_ctx[id].over_ms >= ACT_OVERCURRENT_WINDOW_MS) {
s_ctx[id].status.direction = ACT_DIR_STOP;
s_ctx[id].status.pwm_percent = 0U;
s_ctx[id].status.overcurrent = true;
s_ctx[id].status.last_error = EPB_EOVERCURRENT;
}
} else {
s_ctx[id].over_ms = 0U;
}
/* SWE-015: Klemmkraft aus Peak-Strom schaetzen (nur bei Apply). */
if (s_ctx[id].status.direction == ACT_DIR_APPLY) {
const uint32_t force = ((uint32_t)s_ctx[id].status.peak_current_ma
* ACT_FORCE_PER_AMP_N) / 1000U;
s_ctx[id].status.clamping_force_n =
(force > UINT16_MAX) ? UINT16_MAX : (uint16_t)force;
}
}
+49
View File
@@ -0,0 +1,49 @@
/**
* @file actuator_driver.h
* @brief Low-Level-Ansteuerung der EPB-Aktoren.
*
* @arch SWA-003
* @reqs SWE-006 SWE-013 SWE-014 SWE-015
*
* ASIL: B
*/
#ifndef ACTUATOR_DRIVER_H
#define ACTUATOR_DRIVER_H
#include "epb_types.h"
typedef enum {
ACT_DIR_STOP = 0,
ACT_DIR_APPLY = 1,
ACT_DIR_RELEASE = 2
} ActuatorDirection;
typedef struct {
ActuatorDirection direction;
uint8_t pwm_percent; /* 0..100 */
uint16_t current_ma; /* aktueller Motorstrom */
uint16_t peak_current_ma;
uint16_t clamping_force_n; /* geschaetzt aus Strom */
bool overcurrent;
EpbStatus last_error;
} ActuatorStatus;
/** Strom-Limit (Spec) und Zeitfenster fuer Cutoff (Spec). */
#define ACT_OVERCURRENT_LIMIT_MA 8000U
#define ACT_OVERCURRENT_WINDOW_MS 100U
#define ACT_FORCE_PER_AMP_N 2500U /* lineare Naeherung */
EpbStatus actuator_init(void);
EpbStatus actuator_apply(ActuatorId id, uint8_t pwm_percent);
EpbStatus actuator_release(ActuatorId id, uint8_t pwm_percent);
EpbStatus actuator_stop(ActuatorId id);
ActuatorStatus actuator_get_status(ActuatorId id);
/**
* @brief ISR-Hook fuer Strom-Sampling. Wird mit 1 kHz aufgerufen.
*
* Fuer Tests durch Test-Doubles ersetzt.
*/
void actuator_isr_1khz(ActuatorId id, uint16_t current_sample_ma);
#endif /* ACTUATOR_DRIVER_H */
+153
View File
@@ -0,0 +1,153 @@
/**
* @file apply_controller.c
* @brief Apply/Hold/Release State Machine.
*
* @arch SWA-002
* @reqs SWE-001 SWE-002 SWE-003 SWE-004
*
* ASIL: D. Diese Komponente ist die sicherheitskritische Kernlogik.
* Aenderungen erfordern Technical Review mit 2 Approvals.
*/
#include <stddef.h>
#include "apply_controller.h"
#include "actuator_driver.h"
typedef struct {
EpbState state;
uint8_t step_in_state; /* 50ms-Ticks im aktuellen State */
EpbStatus last_error;
uint32_t step_count; /* Watchdog-Alive-Counter */
} ApplyCtx;
static ApplyCtx s_ctx;
static void enter_state(EpbState new_state)
{
s_ctx.state = new_state;
s_ctx.step_in_state = 0U;
}
static bool release_preconditions_ok(const ApplyInputs* in)
{
/* @reqs SWE-005 (Release-Voraussetzungen) — hier konsumiert */
return in->engine_running
&& in->brake_pedal_pressed
&& in->gear_engaged;
}
static bool apply_request_present(const ApplyInputs* in)
{
return (in->sw_state == SWITCH_APPLY) || in->safety_apply_request;
}
static bool release_request_present(const ApplyInputs* in)
{
return in->sw_state == SWITCH_RELEASE;
}
static uint16_t min_force(const ApplyInputs* in)
{
return (in->left_force_n < in->right_force_n)
? in->left_force_n : in->right_force_n;
}
EpbStatus apply_ctrl_init(void)
{
s_ctx.state = EPB_STATE_RELEASED;
s_ctx.step_in_state = 0U;
s_ctx.last_error = EPB_OK;
s_ctx.step_count = 0U;
return EPB_OK;
}
void apply_ctrl_step_50ms(const ApplyInputs* in)
{
if (in == NULL) {
s_ctx.last_error = EPB_EINVAL;
return;
}
/* SWE-002: Watchdog-Alive-Counter erhoehen */
++s_ctx.step_count;
if (s_ctx.step_in_state < UINT8_MAX) {
++s_ctx.step_in_state;
}
switch (s_ctx.state) {
case EPB_STATE_RELEASED:
if (apply_request_present(in) && in->standstill) {
(void)actuator_apply(ACTUATOR_LEFT, 80U);
(void)actuator_apply(ACTUATOR_RIGHT, 80U);
enter_state(EPB_STATE_APPLYING);
}
break;
case EPB_STATE_APPLYING:
/* SWE-004: Klemmkraft-Erreichen pruefen */
if (min_force(in) >= APPLY_TARGET_FORCE_N) {
(void)actuator_stop(ACTUATOR_LEFT);
(void)actuator_stop(ACTUATOR_RIGHT);
enter_state(EPB_STATE_APPLIED);
} else if (s_ctx.step_in_state >= APPLY_TIMEOUT_50MS) {
s_ctx.last_error = EPB_ETIMEOUT;
(void)actuator_stop(ACTUATOR_LEFT);
(void)actuator_stop(ACTUATOR_RIGHT);
enter_state(EPB_STATE_ERROR);
}
break;
case EPB_STATE_APPLIED:
/* SWE-001: Klemmkraft halten — bei Unterschreitung nachregeln */
if (min_force(in) < (APPLY_TARGET_FORCE_N - HOLD_TOLERANCE_N)) {
(void)actuator_apply(ACTUATOR_LEFT, 60U);
(void)actuator_apply(ACTUATOR_RIGHT, 60U);
enter_state(EPB_STATE_APPLYING);
break;
}
if (release_request_present(in) && release_preconditions_ok(in)) {
(void)actuator_release(ACTUATOR_LEFT, 80U);
(void)actuator_release(ACTUATOR_RIGHT, 80U);
enter_state(EPB_STATE_RELEASING);
}
break;
case EPB_STATE_RELEASING:
if (min_force(in) < HOLD_TOLERANCE_N) {
(void)actuator_stop(ACTUATOR_LEFT);
(void)actuator_stop(ACTUATOR_RIGHT);
enter_state(EPB_STATE_RELEASED);
} else if (s_ctx.step_in_state >= (APPLY_TIMEOUT_50MS - 6U)) {
s_ctx.last_error = EPB_ETIMEOUT;
(void)actuator_stop(ACTUATOR_LEFT);
(void)actuator_stop(ACTUATOR_RIGHT);
enter_state(EPB_STATE_ERROR);
}
break;
case EPB_STATE_ERROR:
default:
/* Auf Reset warten; sicherer Zustand ist Apply, also kein Release */
if (!apply_request_present(in) && !release_request_present(in)) {
s_ctx.last_error = EPB_OK;
enter_state(EPB_STATE_RELEASED);
}
break;
}
}
EpbState apply_ctrl_get_state(void)
{
return s_ctx.state;
}
EpbStatus apply_ctrl_last_error(void)
{
return s_ctx.last_error;
}
uint32_t apply_ctrl_get_step_count(void)
{
return s_ctx.step_count;
}
+43
View File
@@ -0,0 +1,43 @@
/**
* @file apply_controller.h
* @brief Apply/Hold/Release-Steuerung der EPB.
*
* @arch SWA-002
* @reqs SWE-001 SWE-002 SWE-003 SWE-004
*
* ASIL: D
*/
#ifndef APPLY_CONTROLLER_H
#define APPLY_CONTROLLER_H
#include "epb_types.h"
typedef struct {
SwitchState sw_state; /* aus SwitchDebouncer */
bool standstill; /* aus Wheel-Speed-Plausi */
bool engine_running;
bool brake_pedal_pressed;
bool gear_engaged;
bool safety_apply_request; /* aus SafetyManager */
uint16_t left_force_n;
uint16_t right_force_n;
} ApplyInputs;
#define APPLY_TARGET_FORCE_N 12000U /* Ziel-Klemmkraft je Aktor */
#define APPLY_TIMEOUT_50MS 30U /* 30 * 50ms = 1500 ms */
#define HOLD_TOLERANCE_N 1200U /* 10% von Ziel */
EpbStatus apply_ctrl_init(void);
/**
* @brief Step-Funktion 50 ms.
*
* @reqs SWE-001 SWE-002 SWE-003 SWE-004
*/
void apply_ctrl_step_50ms(const ApplyInputs* in);
EpbState apply_ctrl_get_state(void);
EpbStatus apply_ctrl_last_error(void);
uint32_t apply_ctrl_get_step_count(void); /* Watchdog-Alive-Counter */
#endif /* APPLY_CONTROLLER_H */
+47
View File
@@ -0,0 +1,47 @@
/**
* @file epb_types.h
* @brief Gemeinsame Typen fuer die EPB-Software.
*
* @arch SA-001
*/
#ifndef EPB_TYPES_H
#define EPB_TYPES_H
#include <stdbool.h>
#include <stdint.h>
typedef enum {
EPB_OK = 0,
EPB_EINVAL = 1,
EPB_ETIMEOUT = 2,
EPB_ENOSPACE = 3,
EPB_EHARDWARE = 4,
EPB_EOVERCURRENT = 5
} EpbStatus;
typedef enum {
EPB_STATE_RELEASED = 0,
EPB_STATE_APPLYING = 1,
EPB_STATE_APPLIED = 2,
EPB_STATE_RELEASING = 3,
EPB_STATE_ERROR = 0xFF
} EpbState;
typedef enum {
ACTUATOR_LEFT = 0,
ACTUATOR_RIGHT = 1,
ACTUATOR_COUNT = 2
} ActuatorId;
typedef enum {
SWITCH_NEUTRAL = 0,
SWITCH_APPLY = 1,
SWITCH_RELEASE = 2
} SwitchState;
typedef struct {
uint8_t apply_raw : 1;
uint8_t release_raw : 1;
} SwitchRaw;
#endif /* EPB_TYPES_H */
+18
View File
@@ -0,0 +1,18 @@
# Stubs — nicht implementierte Komponenten
Diese Komponenten sind in der Software-Architektur (`arch/swe/`) vollstaendig
spezifiziert, aber in dieser Demo nicht ausimplementiert. Sie zeigen nur die
Header-Schnittstellen.
Im Real-Projekt wuerden hier vollstaendige `.c`-Implementierungen plus
Unit-Tests stehen.
| Komponente | ASIL | SWA |
|-------------------------|------|-----------|
| Safety Manager | D | SWA-001 |
| Wheel Speed Plausi | B | SWA-004 |
| Inclinometer Filter | B | SWA-005 |
| Display Manager | QM | SWA-007 |
| Diagnostic Manager | QM | SWA-008 |
| Service Mode | QM | SWA-009 |
| Logger | QM | SWA-010 |
+19
View File
@@ -0,0 +1,19 @@
/**
* @file diag_manager.h
* @brief UDS-Diagnose nach ISO 14229.
*
* @arch SWA-008
* @reqs SWE-018 SWE-019
*
* ASIL: QM. STUB.
*/
#ifndef DIAG_MANAGER_H
#define DIAG_MANAGER_H
#include "../epb_types.h"
EpbStatus diag_init(void);
void diag_handle_request(const uint8_t* data, uint16_t len);
void diag_set_dtc(uint16_t dtc_id);
#endif
+19
View File
@@ -0,0 +1,19 @@
/**
* @file display_manager.h
* @brief LED + CAN-Status-Frame Steuerung.
*
* @arch SWA-007
* @reqs SWE-020 SWE-021
*
* ASIL: QM. STUB.
*/
#ifndef DISPLAY_MANAGER_H
#define DISPLAY_MANAGER_H
#include "../epb_types.h"
EpbStatus display_init(void);
void display_set_status(EpbState s);
void display_step_20ms(void);
#endif
+19
View File
@@ -0,0 +1,19 @@
/**
* @file inclinometer.h
* @brief Inclinometer-Tiefpass-Filter.
*
* @arch SWA-005
* @reqs SWE-024
*
* ASIL: B. STUB.
*/
#ifndef INCLINOMETER_H
#define INCLINOMETER_H
#include "../epb_types.h"
EpbStatus inclino_init(void);
void inclino_step_10ms(int16_t raw_mdeg);
float inclino_get_grade_percent(void);
#endif
+24
View File
@@ -0,0 +1,24 @@
/**
* @file logger.h
* @brief Logger — Ringpuffer + EEPROM-Persistenz.
*
* @arch SWA-010
*
* ASIL: QM. STUB.
*/
#ifndef LOGGER_H
#define LOGGER_H
#include "../epb_types.h"
typedef enum {
LOG_DEBUG = 0,
LOG_INFO,
LOG_WARN,
LOG_ERROR
} LogLevel;
EpbStatus log_init(void);
void log_event(LogLevel lvl, uint16_t event_id, uint32_t param);
#endif
+27
View File
@@ -0,0 +1,27 @@
/**
* @file safety_manager.h
* @brief Safety Manager — Hill-Hold + Auto-Apply Logik.
*
* @arch SWA-001
* @reqs SWE-007 SWE-008 SWE-009 SWE-010
*
* ASIL: D. STUB — nicht implementiert in dieser Demo.
*/
#ifndef SAFETY_MANAGER_H
#define SAFETY_MANAGER_H
#include "../epb_types.h"
typedef struct {
bool engine_running;
bool brake_pedal_pressed;
float vehicle_speed_kmh;
float grade_percent;
EpbState current_state;
} SafetyInputs;
EpbStatus safety_mgr_init(void);
void safety_mgr_step_50ms(const SafetyInputs* in);
bool safety_mgr_apply_requested(void);
#endif
+20
View File
@@ -0,0 +1,20 @@
/**
* @file service_mode.h
* @brief Service-Modus fuer Werkstatt (Bremsbelag-Wechsel).
*
* @arch SWA-009
* @reqs SWE-016 SWE-017
*
* ASIL: QM. STUB.
*/
#ifndef SERVICE_MODE_H
#define SERVICE_MODE_H
#include "../epb_types.h"
EpbStatus service_mode_init(void);
EpbStatus service_mode_activate(void);
EpbStatus service_mode_deactivate(void);
bool service_mode_is_active(void);
#endif
+27
View File
@@ -0,0 +1,27 @@
/**
* @file wheel_speed_plausi.h
* @brief Wheel-Speed-Plausibilisierung + Stillstands-Erkennung.
*
* @arch SWA-004
* @reqs SWE-022 SWE-023
*
* ASIL: B. STUB.
*/
#ifndef WHEEL_SPEED_PLAUSI_H
#define WHEEL_SPEED_PLAUSI_H
#include "../epb_types.h"
typedef struct {
float fl_kmh;
float fr_kmh;
float rl_kmh;
float rr_kmh;
} WheelInputs;
EpbStatus wheel_speed_init(void);
void wheel_speed_step_10ms(const WheelInputs* in);
bool wheel_speed_is_standstill(void);
float wheel_speed_get_vehicle(void);
#endif
+60
View File
@@ -0,0 +1,60 @@
/**
* @file switch_debouncer.c
* @brief Implementierung des EPB-Schalter-Debouncers.
*
* @arch SWA-006
* @reqs SWE-025
*
* ASIL: QM.
*/
#include "switch_debouncer.h"
typedef struct {
SwitchState current;
SwitchState candidate;
uint8_t candidate_count;
} DebouncerCtx;
static DebouncerCtx s_ctx;
static SwitchState raw_to_candidate(SwitchRaw raw)
{
if (raw.apply_raw && !raw.release_raw) {
return SWITCH_APPLY;
}
if (raw.release_raw && !raw.apply_raw) {
return SWITCH_RELEASE;
}
return SWITCH_NEUTRAL;
}
EpbStatus switch_init(void)
{
s_ctx.current = SWITCH_NEUTRAL;
s_ctx.candidate = SWITCH_NEUTRAL;
s_ctx.candidate_count = 0U;
return EPB_OK;
}
void switch_step_10ms(SwitchRaw raw)
{
const SwitchState observed = raw_to_candidate(raw);
if (observed == s_ctx.candidate) {
if (s_ctx.candidate_count < SWITCH_DEBOUNCE_SAMPLES) {
++s_ctx.candidate_count;
}
} else {
s_ctx.candidate = observed;
s_ctx.candidate_count = 1U;
}
if (s_ctx.candidate_count >= SWITCH_DEBOUNCE_SAMPLES) {
s_ctx.current = s_ctx.candidate;
}
}
SwitchState switch_get_state(void)
{
return s_ctx.current;
}
+22
View File
@@ -0,0 +1,22 @@
/**
* @file switch_debouncer.h
* @brief EPB-Schalter mit Software-Entprellung.
*
* @arch SWA-006
* @reqs SWE-025
*
* ASIL: QM
*/
#ifndef SWITCH_DEBOUNCER_H
#define SWITCH_DEBOUNCER_H
#include "epb_types.h"
/** Step-Zyklus 10 ms. Debounce-Schwelle 50 ms (5 stabile Samples). */
#define SWITCH_DEBOUNCE_SAMPLES 5
EpbStatus switch_init(void);
void switch_step_10ms(SwitchRaw raw);
SwitchState switch_get_state(void);
#endif /* SWITCH_DEBOUNCER_H */