Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,18 @@ Der geführte Modus ist ein didaktisches Angebot, keine Speicherung oder Benotun
Im **freien Labor** erscheinen Registersteuerung, CNT-Zähler, Zeitdiagramm und Kennwerte zuerst. Danach folgen die vollständigen Vertiefungen für **RCC-Taktbaum** und **Prescaler**; ihre Rechen- und Eingabefunktionen bleiben unverändert. Die Sprungnavigation bedient die Bereiche ohne Routenwechsel (die Anwendung nutzt Hash-Routing) und bewegt den Tastaturfokus zum gewählten Abschnitt. Aus den Vertiefungen gibt es einen direkten Rückweg zu den Registern.

Register-Eingaben referenzieren ihre Erklärtexte über `aria-describedby`; mobile Layouts, Textgrößen und Tasten sind auf **320 bis 1280 px** ausgerichtet. E2E prüft die Navigation, die unabhängige ARR/CCR2-Bearbeitung und die horizontale Breite in beiden Modi. Diese Änderungen betreffen Layout/Bedienbarkeit, **nicht** die Timer-Berechnungen.

## Virtuelles DOT an PE11: Signalweg (Issue #5)

Der Abschnitt „Virtuelles DOT · PE11“ besitzt ein **standardmäßig geschlossenes, per Tastatur zugängliches Ausklappfeld** mit einem dynamischen **Signalweg und einer Zustandstabelle**. Er trennt das **interne OC2REF-Signal**, die Ausgangspolarität `TIM_OCPOLARITY_LOW`, den **elektrischen PE11-Pegel** und die **low-aktive DOT-Anzeige**.

- PWM Mode 1: `OC2REF = 1` für `CNT < CCR2` (gemeinsamer Berechnungspfad `pwmActive`).
- Bei `TIM1_CH2` mit `OCPolarity LOW` entspricht OC2REF=1 einem **LOW-Pegel an PE11**; eine low-aktive DOT-Leitung lässt das gewählte Segment dann leuchten.
- Die physikalische Darstellung gilt nur bei **TIM1** und unter Annahme aktivierter Ausgangsfreigaben **CC2E/MOE**, AF1-Konfiguration sowie einer aktivierten Anzeigenstelle über **CNTL1…CNTL4**. Diese Voraussetzungen sind in der Website nicht dynamisch modelliert. **TIM2_CH2 ist nicht mit DOT/PE11 verbunden.**
- Das Blockschaltbild ist eine **didaktische Funktionsdarstellung, kein elektrischer Stromlaufplan** und behauptet keine zusätzlichen physischen Inverter. Der tatsächliche Treiber-/Multiplex-Strompfad des 8-Segment-Adapterboards ist nicht rekonstruiert.
- **Primärquelle der Praktikumshardware:** *Praktikum Eingebettete Systeme – Praktikumsanleitung*, Technische Hochschule Mittelhessen, **Version 1.6 vom 13.10.2026**, Kap. **7.1.1**, Druckseiten **28–29/79** (low-aktive Segmentleitungen, CNTL1…CNTL4, Verweis auf das 8-Segment-Board-Schematic), sowie Kap. **7.6.3**, Druckseiten **51–52/79** (TIM1_CH2 → AF1 → PE11 → DOT). Die konkrete Kurs-PDF liegt in den Praktikumsressourcen, **nicht als öffentlich verlinkte Kopie** in diesem Repository.
- **Offizielle Hardwaredokumente:** [ST STM32F429-Datenblatt, Pin-/AF-Tabelle (PE11 / TIM1_CH2)](https://www.st.com/resource/en/datasheet/stm32f429zg.pdf), [Waveshare Open429Z-D – Ressourcen inkl. Basisschaltplan](https://www.waveshare.com/wiki/Open429Z-D) und [Waveshare 8 SEG LED Board – Ressourcen inkl. Adapterschaltplan](https://www.waveshare.com/wiki/8_SEG_LED_Board). Das vorliegende Open429Z-D-Basisschaltbild von 2014 belegt **nicht** den vollständigen Strompfad des Adapterboards; dieser wurde für die Demo nicht elektrisch rekonstruiert.

Das vorhandene OC2REF-Zeitdiagramm und die Simulations-Engine bleiben unverändert; die Tabelle bietet die alternative synchrone Darstellung der drei Pegel-/LED-Ebenen und reagiert auf CNT, CCR2 und Timerwechsel.

**TIM2-Darstellung:** Das geschlossene Ausklappfeld und beide LED-Widgets weisen ausdrücklich auf die fehlende Verbindung zu DOT/PE11 hin. Die TIM1-Pegeltabelle wird bei ausgewähltem TIM2 nicht angezeigt, damit keine hypothetischen PE11-HIGH-/LOW-Werte als aktueller TIM2-Pegel missverstanden werden.
99 changes: 99 additions & 0 deletions e2e/visual.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,3 +478,102 @@ test("Sprungziele bleiben bei Desktop und 320 px unterhalb des Sticky Headers si
}
}
});

test("DOT-Signalweg: interne Referenz, PE11-Pegel und low-aktive LED bleiben synchron", async ({ page }, info) => {
await page.goto("/PES-Demos/#/timer-pwm");
await page.getByRole("button", { name: "Freies Labor", exact: true }).click();
const details = page.getByTestId("dot-signal-path");
const summary = details.locator("summary");
await expect(details).not.toHaveAttribute("open", "");
await expect(summary).toContainText("Warum leuchtet DOT bei OC2REF = 1 (im TIM1-Modell)?");
await summary.click();
await expect(details).toHaveAttribute("open", "");
await expect(details.getByTestId("path-oc2ref")).toContainText("OC2REF = 1");
await expect(details.getByTestId("path-pe11")).toContainText("PE11: LOW (0)");
await expect(details.getByTestId("path-dot")).toContainText("DOT: AN");
await expect(details.locator(".dot-path-row-active")).toContainText("LOW (0)");
await expect(details).toContainText("CC2E und MOE für TIM1");
await expect(details).toContainText("kein verifizierter elektrischer Schaltplan");
await details.scrollIntoViewIfNeeded();
await capture(page, info.project.name, "dot-path-tim1-on");

const counterStep = page.locator(".transport-buttons")
.getByRole("button", { name: "+1 CNT-Zählschritt" });
for (let i = 0; i < 3; i++) await counterStep.click();
await expect(page.locator(".counter-number")).toHaveText("3");
await expect(details).toHaveAttribute("open", "");
await expect(details.getByTestId("path-oc2ref")).toContainText("OC2REF = 0");
await expect(details.getByTestId("path-pe11")).toContainText("PE11: HIGH (1)");
await expect(details.getByTestId("path-dot")).toContainText("DOT: AUS");
await expect(details.locator(".dot-path-row-active")).toContainText("HIGH (1)");
await details.scrollIntoViewIfNeeded();
await capture(page, info.project.name, "dot-path-tim1-off");

await page.locator(".transport-buttons").getByRole("button", { name: /Zum Rücksprung/ }).click();
await expect(page.locator(".counter-number")).toHaveText("0");
await expect(details.getByTestId("path-dot")).toContainText("DOT: AN");

// Boundary cases: no active step at CCR2=0 and always active when CCR2>ARR.
await page.locator("#ccr").fill("0");
await expect(details.getByTestId("path-dot")).toContainText("DOT: AUS");
await expect(details.getByTestId("path-pe11")).toContainText("PE11: HIGH (1)");
await page.locator("#ccr").fill("12");
await expect(details.getByTestId("path-dot")).toContainText("DOT: AN");
await expect(details.getByTestId("path-pe11")).toContainText("PE11: LOW (0)");

await page.locator("#timer").selectOption("TIM2");
await expect(details.getByTestId("path-oc2ref")).toContainText("OC2REF = 1");
await expect(details.getByTestId("path-pe11")).toContainText("PE11: nicht verbunden");
await expect(details.getByTestId("path-dot")).toContainText("DOT: nicht verbunden");
await expect(details.getByTestId("path-current-state")).toContainText("keine Verbindung");
await expect(details.locator(".dot-path-table")).toHaveCount(0);
await expect(details.locator(".dot-path-row-active")).toHaveCount(0);
await expect(details.getByTestId("tim2-table-explanation")).toContainText("ausschließlich für das TIM1-Beispiel");
await expect(page.locator(".led-visual").first().getByRole("img")).toHaveAttribute(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2 · Tests sichern aktuell einen widersprüchlichen TIM2-Zustand ab] Der TIM2-Test kontrolliert den neuen Pfad auf „nicht verbunden“, verlangt zugleich aber beim übergeordneten LED-Bild den Accessible Name „LED momentan ausgeschaltet“. Nach Harmonisierung beider Komponenten sollte die Erwartung auf „nicht verbunden“ wechseln. Zusätzlich prüfen: geschlossener Summarytext bei TIM2, keine „aktuell“-Zeile in der TIM1-Vergleichstabelle bei TIM2 und TIM2 auch mit OC2REF=0. Die bisherigen 36 bestandenen Tests decken das positive TIM1-Signal und die wesentlichen PWM-Grenzen bereits gut ab.

"aria-label", "DOT nicht verbunden: TIM2_CH2 hat keinen Signalpfad über PE11");
await expect(page.locator(".led-visual").nth(1).getByRole("img")).toHaveAttribute(
"aria-label", "Kein Helligkeitsmittelwert: TIM2_CH2 ist nicht mit dem DOT an PE11 verbunden");
await expect(page.locator(".led-visual").first()).toContainText("Nicht verbunden");
await expect(page.locator(".led-visual").nth(1)).toContainText("Kein Mittelwert");

// Auch wenn das interne Referenzsignal auf 0 wechselt, bleibt der PE11-Pfad getrennt.
await page.locator("#ccr").fill("0");
await expect(details.getByTestId("path-oc2ref")).toContainText("OC2REF = 0");
await expect(details.getByTestId("path-pe11")).toContainText("PE11: nicht verbunden");
await expect(details.getByTestId("path-dot")).toContainText("DOT: nicht verbunden");
await expect(details.locator(".dot-path-table")).toHaveCount(0);
await expect(page.locator(".led-visual").first().getByRole("img")).toHaveAttribute(
"aria-label", "DOT nicht verbunden: TIM2_CH2 hat keinen Signalpfad über PE11");

// Zusammengeklappt ist bereits der erste Erklärungssatz timerabhängig.
await summary.focus();
await page.keyboard.press("Enter");
await expect(details).not.toHaveAttribute("open", "");
await expect(summary).toContainText("Warum hat TIM2_CH2 keinen DOT-Pfad über PE11?");
await details.scrollIntoViewIfNeeded();
await capture(page, info.project.name, "dot-path-tim2-disconnected");

// A native details/summary is operable with a keyboard in both directions.
await page.keyboard.press("Enter");
await expect(details).toHaveAttribute("open", "");
});

test("DOT-Signalweg ist mobil aufklappbar ohne horizontale Überbreite", async ({ page }, info) => {
await page.goto("/PES-Demos/#/timer-pwm");
await page.getByRole("button", { name: "Freies Labor", exact: true }).click();
const details = page.getByTestId("dot-signal-path");
await details.locator("summary").click();
for (const width of [320, 360, 393]) {
await page.setViewportSize({ width, height: 800 });
await expect(details.getByTestId("path-dot")).toBeVisible();
const dimensions = await page.evaluate(() => ({
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
stageOverflow: [...document.querySelectorAll(".dot-path-stage")]
.map(el => el.scrollWidth - el.clientWidth),
}));
expect(dimensions.documentOverflow, "Dokument bei " + width + "px").toBeLessThanOrEqual(2);
expect(Math.max(...dimensions.stageOverflow), "Signalweg-Knoten bei " + width + "px").toBeLessThanOrEqual(2);
await details.scrollIntoViewIfNeeded();
await capture(page, info.project.name, "dot-path-mobile-" + width);
}
});
23 changes: 16 additions & 7 deletions src/demos/timer-pwm/TimerPwmDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Waveform from "./components/Waveform";
import ClockTree from "./components/ClockTree";
import PrescalerPipeline from "./components/PrescalerPipeline";
import GuidedTimerLesson from "./components/GuidedTimerLesson";
import DotSignalPath from "./components/DotSignalPath";
import {
advanceTimerClocks, INITIAL_CLOCKED_COUNTER, type ClockedCounterState,
} from "./model/prescaler";
Expand Down Expand Up @@ -455,28 +456,36 @@ export default function TimerPwmDemo() {
</section>

<section className="panel physical-panel">
<div>
<div className="eyebrow">05 / HARDWAREBEZUG</div>
<div className="physical-summary">
<div>
<div className="eyebrow">05 / HARDWAREBEZUG</div>
<h2>Virtuelles DOT · PE11</h2>
<p>{pe11Connected
? "TIM1_CH2 → AF1 → PE11. Die LED ist low-aktiv. Wir setzen eine aktivierte Anzeigenstelle und freigeschalteten TIM1-Ausgang voraus (CC2E/MOE)."
: "TIM2_CH2 liegt nicht auf PE11. Deshalb bleibt das virtuelle DOT-Segment unverbunden; oben wird trotzdem das interne PWM-Signal simuliert."}</p>
<p className="field-hint">Links: momentaner LED-Zustand gemäß CNT. Rechts: gemitteltes Helligkeitsmodell, nur bei ausreichend hoher PWM-Frequenz sinnvoll.</p>
<p className="field-hint">{pe11Connected
? "Links: momentaner LED-Zustand gemäß CNT. Rechts: gemitteltes Helligkeitsmodell, nur bei ausreichend hoher PWM-Frequenz sinnvoll."
: "Beide LED-Anzeigen sind für TIM2 nicht verbunden; es wird kein PE11-Pegel und kein LED-Mittelwert simuliert."}</p>
</div>
<div className="led-pair">
<div className="led-visual">
<div className="led-frame" role="img" aria-label={"LED momentan " + (pe11Connected && active ? "eingeschaltet" : "ausgeschaltet")}>
<div className="led-frame" role="img" aria-label={!pe11Connected ? "DOT nicht verbunden: TIM2_CH2 hat keinen Signalpfad über PE11" : "LED momentan " + (active ? "eingeschaltet" : "ausgeschaltet")}>
<span className="led" style={{ opacity: pe11Connected && active ? 1 : 0.12, boxShadow: pe11Connected && active ? "0 0 25px rgba(245,65,78,.75)" : "none" }}/>
</div>
<span>Momentan</span>
<span>{pe11Connected ? "Momentan" : "Nicht verbunden"}</span>
</div>
<div className="led-visual">
<div className="led-frame" role="img" aria-label="Vereinfachtes Modell der zeitlich gemittelten Helligkeit">
<div className="led-frame" role="img" aria-label={pe11Connected ? "Vereinfachtes Modell der zeitlich gemittelten Helligkeit" : "Kein Helligkeitsmittelwert: TIM2_CH2 ist nicht mit dem DOT an PE11 verbunden"}>
<span className="led" style={{ opacity: pe11Connected && result ? Math.max(0.12, result.duty) : 0.12, boxShadow: pe11Connected && result ? "0 0 " + (3 + 25 * result.duty) + "px rgba(245,65,78,.55)" : "none" }}/>
</div>
<span>Mittelwert</span>
<span>{pe11Connected ? "Mittelwert" : "Kein Mittelwert"}</span>
</div>
</div>
</div>
<DotSignalPath
timer={timer} cnt={counter.cnt} arr={arr} psc={psc} ccr={ccr}
oc2refActive={active}
/>
</section>

<section className="panel">
Expand Down
Loading
Loading