Files
ccu-demonstrator-msp430/index.html
2026-01-02 20:32:26 +01:00

1941 lines
67 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
// ----------------------------------------------- //
// (Capture-)Compare-Simulation von Tim van den Boom //
// ~~~~ //
// Studienarbeit TEL23AEO 2025 im 5. Semester //
// Stand: 25.12.2ß25 //
// ----------------------------------------------- //
In diesem Code kommt generative KI zum Einsatz
der Abschnitt, der dies betrifft, ist entsprechend markiert.
-->
<html>
<head>
<title>Demonstrator zur CCU</title>
</head>
<body onload="defaults();">
<div id="demo-wrapper">
<canvas id="main-canvas"></canvas>
</div>
<div id="controls">
<div class="control-tab-wrapper">
<div class="tab-list" id="tab-list-5">
<div onclick="changeTab(5,0)" id="indicator-5-0" class="active">TAxCTL</div>
<div onclick="changeTab(5,1)" id="indicator-5-1">Zoom</div>
<div onclick="changeTab(5,2)" id="indicator-5-2">Startwert</div>
</div>
<div class="bin-hex-dec-display" id="tab-5-0" style="display: block;">
<input type="text" id="decimalValue-TAxCTL" value="MC_1 | ID_0 | TAIE | TASSEL_0" oninput="updateValueDisplay('TAxCTL','text',true)"/>
<span id="bin-hex-dec-display-TAxCTL-bin"></span>
<span id="bin-hex-dec-display-TAxCTL-hex"></span>
<span id="bin-hex-dec-display-TAxCTL-dec"></span>
<div class="info-box explain" id="explain-TAxCTL">
<img src="vendor/light-bulb-on.svg" />
<div id="explain-TAxCTL-text"></div>
</div>
<div class="info-box warning" id="warning-TAxCTL" style="display: none;">
<img src="vendor/warning-triangle.svg" />
<div id="warning-TAxCTL-text"></div>
</div>
<div class="info-box warning" id="warning-TAxCTL-1" style="display: none;">
<img src="vendor/warning-triangle.svg" />
<div id="warning-TAxCTL-1-text"></div>
</div>
</div>
<div class="bin-hex-dec-display" id="tab-5-1" style="display: none;">
<input id="slider_zoom" type="range" min="25" max="300" value="250" oninput="updateValueDisplay('zoom','slider',true)"/>
<span id="zoom-display-perc" "></span>
<span id="zoom-display-rel"></span>
</div>
<div class="bin-hex-dec-display" id="tab-5-2" style="display: none;">
<input id="startValue" type="checkbox" oninput="drawCanvas()"/>
<div class="info-box explain" >
<img src="vendor/light-bulb-on.svg" />
<div>
Wenn notwendig, können Sie hier einen durchgängig eingeschwungenen Zustand simulieren, sofern Ihr gewählter Output-Mode dies erfordert.<br>Die Ausgänge werden dann mit dem entsprechenden Startwert beschaltet.
</div>
</div>
</div>
</div>
<div class="control-tab-wrapper">
<div class="tab-list" id="tab-list-6">
<div onclick="changeTab(6,0)" id="indicator-6-0" class="active">TAxCCTL0</div>
<div onclick="changeTab(6,1)" id="indicator-6-1">TAxCCTL1</div>
<div onclick="changeTab(6,2)" id="indicator-6-2">TAxCCTL2</div>
</div>
<div class="bin-hex-dec-display" id="tab-6-0" style="display: block;">
<input type="text" id="decimalValue-TAxCCTL0" value="OUTMOD_1" oninput="updateValueDisplay('TAxCCTL0','text',true)"/>
<span id="bin-hex-dec-display-TAxCCTL0-bin"></span>
<span id="bin-hex-dec-display-TAxCCTL0-hex"></span>
<span id="bin-hex-dec-display-TAxCCTL0-dec"></span>
<div class="info-box explain" id="explain-TAxCCTL0">
<img src="vendor/light-bulb-on.svg" />
<div id="explain-TAxCCTL0-text"></div>
</div>
<div class="info-box warning" id="warning-TAxCCTL0" style="display: none;">
<img src="vendor/warning-triangle.svg" />
<div id="warning-TAxCCTL0-text"></div>
</div>
<div class="info-box warning" id="warning-TAxCCTL0-1" style="display: none;">
<img src="vendor/warning-triangle.svg" />
<div id="warning-TAxCCTL0-1-text"></div>
</div>
</div>
<div class="bin-hex-dec-display" id="tab-6-1" style="display: none;">
<input type="text" id="decimalValue-TAxCCTL1" value="OUTMOD_0 | OUT" oninput="updateValueDisplay('TAxCCTL1','text',true)"/>
<span id="bin-hex-dec-display-TAxCCTL1-bin"></span>
<span id="bin-hex-dec-display-TAxCCTL1-hex"></span>
<span id="bin-hex-dec-display-TAxCCTL1-dec"></span>
<div class="info-box explain" id="explain-TAxCCTL1">
<img src="vendor/light-bulb-on.svg" />
<div id="explain-TAxCCTL1-text"></div>
</div>
<div class="info-box warning" id="warning-TAxCCTL1" style="display: none;">
<img src="vendor/warning-triangle.svg" />
<div id="warning-TAxCCTL1-text"></div>
</div>
<div class="info-box warning" id="warning-TAxCCTL1-1" style="display: none;">
<img src="vendor/warning-triangle.svg" />
<div id="warning-TAxCCTL1-1-text"></div>
</div>
</div>
<div class="bin-hex-dec-display" id="tab-6-2" style="display: none;">
<input type="text" id="decimalValue-TAxCCTL2" value="OUTMOD1 | OUTMOD2" oninput="updateValueDisplay('TAxCCTL2','text',true)"/>
<span id="bin-hex-dec-display-TAxCCTL2-bin"></span>
<span id="bin-hex-dec-display-TAxCCTL2-hex"></span>
<span id="bin-hex-dec-display-TAxCCTL2-dec"></span>
<div class="info-box explain" id="explain-TAxCCTL2">
<img src="vendor/light-bulb-on.svg" />
<div id="explain-TAxCCTL2-text">
</div>
</div>
<div class="info-box warning" id="warning-TAxCCTL2" style="display: none;">
<img src="vendor/warning-triangle.svg" />
<div id="warning-TAxCCTL2-text"></div>
</div>
<div class="info-box warning" id="warning-TAxCCTL2-1" style="display: none;">
<img src="vendor/warning-triangle.svg" />
<div id="warning-TAxCCTL2-1-text"></div>
</div>
</div>
</div>
<div class="control-tab-wrapper" style="margin-bottom: 5px">
<div class="tab-list" id="tab-list-1">
<div onclick="changeTab(1,0)" id="indicator-1-0" class="active">TAxCCR0</div>
<div onclick="changeTab(1,1)" id="indicator-1-1">TAxCCR1</div>
<div onclick="changeTab(1,2)" id="indicator-1-2">TAxCCR2</div>
</div>
<div class="bin-hex-dec-display" id="tab-1-0" style="display: block;">
<input type="text" id="decimalValue-TAxCCR0" value="0xBFFF" oninput="updateValueDisplay('TAxCCR0','text')"/>
<span id="bin-hex-dec-display-TAxCCR0-bin"></span>
<span id="bin-hex-dec-display-TAxCCR0-hex"></span>
<span id="bin-hex-dec-display-TAxCCR0-dec"></span>
<input id="slider_TAxCCR0" type="range" min="0" max="65535" oninput="updateValueDisplay('TAxCCR0','slider')"/>
</div>
<div class="bin-hex-dec-display" id="tab-1-1" style="display: none;">
<input type="text" id="decimalValue-TAxCCR1" value="0x7FFF" oninput="updateValueDisplay('TAxCCR1','text')" onload="updateValueDisplay('TAxCCR1','text')"/>
<span id="bin-hex-dec-display-TAxCCR1-bin"></span>
<span id="bin-hex-dec-display-TAxCCR1-hex"></span>
<span id="bin-hex-dec-display-TAxCCR1-dec"></span>
<input id="slider_TAxCCR1" type="range" min="0" max="65535" oninput="updateValueDisplay('TAxCCR1','slider')"/>
</div>
<div class="bin-hex-dec-display" id="tab-1-2" style="display: none;">
<input type="text" id="decimalValue-TAxCCR2" value="0x3FFF" oninput="updateValueDisplay('TAxCCR2','text')"/>
<span id="bin-hex-dec-display-TAxCCR2-bin"></span>
<span id="bin-hex-dec-display-TAxCCR2-hex"></span>
<span id="bin-hex-dec-display-TAxCCR2-dec"></span>
<input id="slider_TAxCCR2" type="range" min="0" max="65535" oninput="updateValueDisplay('TAxCCR2','slider')"/>
</div>
</div>
<i style="font-size: 10px; color: grey;">Studienarbeit von Tim van den Boom im Studiengang Elektrotechnik, 2025</i>
<input id="TAxCCR0" type="hidden"/>
<input id="TAxCCR1" type="hidden"/>
<input id="TAxCCR2" type="hidden"/>
<input id="TAxCCTL0" type="hidden"/>
<input id="TAxCCTL1" type="hidden"/>
<input id="TAxCCTL2" type="hidden"/>
<input id="timerMode" type="hidden" value="1"/>
<input id="outputMode0" type="hidden" value="4"/>
<input id="outputMode1" type="hidden" value="4"/>
<input id="outputMode2" type="hidden" value="4"/>
<input id="ramp" value="0" type="hidden"/>
<input id="zoom" value="250" type="hidden"/>
<input id="startValue" type="hidden"/>
<input id="TAxCTL" type="hidden"/>
<input id="TAIE" type="hidden"/>
<input id="TASSEL" type="hidden"/>
<input id="OUT0" type="hidden"/>
<input id="OUT1" type="hidden"/>
<input id="OUT2" type="hidden"/>
</div>
</body>
</html>
<style>
@font-face {
font-family: 'Roboto Mono';
src: url('vendor/RobotoMono.ttf');
}
* {
font-size: 13px;
}
head, body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
font-family: Arial;
}
div#demo-wrapper, div#controls {
padding: 15px;
box-sizing: border-box;
display: inline-block;
float: left;
}
/*
Hier könnte man noch etwas anpassen.
Soll bspw. die Grafik etwas breiter dargestellt werden:
div#demo-wrapper {
width: 60%;
max-width: 90vh;
}
div#controls {
width: 40%;
}
*/
div#demo-wrapper {
width: 50%;
max-width: 90vh;
}
div#controls {
width: 50%;
max-width: 400px;
}
div#controls div.right, div#controls div.left {
width: 100%;
}
div.control-tab-wrapper {
padding: 0;
text-align: center;
margin-bottom: 15px;
}
div.control-tab-wrapper div.tab-list {
width: 100%;
height: 30px;
}
div.control-tab-wrapper div.tab-list div {
display: inline-block;
padding: 5px;
float: left;
height: 100%;
cursor: pointer;
background-color: #F5F5F5;
margin-right: 5px;
border-radius: 5px 5px 0 0 ;
}
div.control-tab-wrapper div.tab-list div:not(.active) {
height: 15px;
}
div.control-tab-wrapper div.tab-list div.active {
background-color: #E8E8E8;
}
div.bin-hex-dec-display {
padding: 6px;
text-align: center;
background-color: #E8E8E8;
border-radius: 10px;
}
div.bin-hex-dec-display div.expandable-content {
display: none;
}
div.bin-hex-dec-display span {
display: inline-block;
width: calc(25% - 6px);
font-family: "Roboto Mono";
padding: 3px;
margin: 5px 5px 5px 0px;
box-sizing: border-box;
background-color: #F5F5F5;
border-radius: 5px;
}
div.bin-hex-dec-display span:first-of-type, div.bin-hex-dec-display span#zoom-display-rel {
width: calc(50% - 6px);
}
div.bin-hex-dec-display span:last-of-type {
margin-right: 0px;
}
div.bin-hex-dec-display input {
width: 100%;
}
div.bin-hex-dec-display select, div.bin-hex-dec-display input[type="text"] {
width: 100%;
background-color: #F5F5F5;
border: none;
border-radius: 5px;
padding: 5px;
border: 0px solid #BFBFBF;
}
div.bin-hex-dec-display input {
display: block;
float: none;
margin: 0;
}
br {
display: block;
margin-bottom: 1em;
content: "";
}
div.info-box {
width: 100%;
border-radius: 7px;
padding: 10px;
box-sizing: border-box;
margin: 0;
}
div.info-box.explain {
background-color: #c3d0e8;
}
div.info-box.warning {
background-color: #ea857a;
margin-top: 5px;
}
div.info-box>img {
width: 20px;
margin-bottom: auto;
float: left;
filter: opacity(.8);
}
div.info-box>div {
width: calc(100% - 50px);
display: block;
height: auto;
margin-left: 30px;
}
</style>
<script>
/*
// ---------------------------------------------------- FARBEN ----------------------------------------------------
*/
// Ausschnitt der Farbpalette von https://developer.gnome.org/hig/reference/palette.html
const colors = {
// Blue
"Blue 1": "#99c1f1",
"Blue 2": "#62a0ea",
"Blue 3": "#3584e4",
"Blue 4": "#1c71d8",
"Blue 5": "#1a5fb4",
// Dark Neutrals
"Dark 1": "#77767b",
"Dark 2": "#5e5c64",
"Dark 3": "#3d3846",
"Dark 4": "#241f31",
"Dark 5": "#000000"
};
/*
// ---------------------------------------------------- DEFAULT-WERTE ----------------------------------------------------
*/
function defaults() {
updateValueDisplay('TAxCTL', 'text', true);
updateValueDisplay('zoom', 'slider', true);
updateValueDisplay('TAxCCTL0', 'text', true);
updateValueDisplay('TAxCCTL1', 'text', true);
updateValueDisplay('TAxCCTL2', 'text', true);
updateValueDisplay('TAxCCR0', 'text');
updateValueDisplay('TAxCCR1', 'text');
updateValueDisplay('TAxCCR2', 'text');
}
/*
// ---------------------------------------------------- TAB-WECHSEL ----------------------------------------------------
*/
function changeTab(tabFamily, tab) {
// Alle Tabs und Indikatoren der angegebenen Familie finden
const tabs = document.querySelectorAll(`[id^="tab-${tabFamily}-"]`);
const indicators = document.querySelectorAll(`[id^="indicator-${tabFamily}-"]`);
// Alle Tabs ausblenden
tabs.forEach(el => el.style.display = "none");
// Gewählten Tab anzeigen
const activeTab = document.getElementById(`tab-${tabFamily}-${tab}`);
if (activeTab) activeTab.style.display = "block";
// Indikatoren zurücksetzen
indicators.forEach(el => el.classList.remove("active"));
// Aktiven Indikator aktivieren
const activeIndicator = document.getElementById(`indicator-${tabFamily}-${tab}`);
if (activeIndicator) activeIndicator.classList.add("active");
}
/*
// ---------------------------------------------------- CANVAS-INITIALISIERUNG ----------------------------------------------------
*/
const canvas = document.getElementById("main-canvas");
const ctx = canvas.getContext("2d");
// weil ich die Artefakte beim Verstellen der Fenstergröße nicht in dne Griff bekam, habe ich mir Hilfe von einer KI geholt! Dies hat sofort geklappt.
// dies ist der einzige mit KI generierte Code-Abschnitt!
const logicalWidth = 900;
const logicalHeight = 1020;
canvas.width = logicalWidth;
canvas.height = logicalHeight;
canvas.style.width = "100%";
canvas.style.height = "auto";
//Zeichnung entsprechend skalieren, falls nötig
// Damit Koordinaten immer mit der "logischen" Größe arbeiten
function resizeScale() {
const dpr = window.devicePixelRatio || 1;
const cssWidth = canvas.clientWidth;
const cssHeight = canvas.clientHeight;
canvas.width = cssWidth * dpr;
canvas.height = cssHeight * dpr;
const scaleX = cssWidth / logicalWidth;
const scaleY = cssHeight / logicalHeight;
// Kombinierte Skalierung: (DPR-Korrektur) * (Logische Skalierung)
ctx.setTransform(dpr * scaleX, 0, 0, dpr * scaleY, 0, 0);
}
//Ende KI-Generierter Code!
// Dies ist die Map für die nicht unterstützten Kontroll-Bits (vgl. C-Header-Datei; Auszug)
// diese stehen allesamt im Zusammenhang zum TimerA
// je nachdem, ob das genutzte Register in value[1] enthalten ist, erfolgt die Ausgabe "kenne ich nicht" oder "inkompatibel"
// es wäre unsinnig, wenn bei einem valide genutzten Register (z.B. TAxCTL) eine "Inkompatibilitätsmeldung" (z.B. durch TAxCCTL1) auftauchen würde (so ist etwa 0x0001 doppelt belegt)
const incompatibleBitValueMap = {
"TASSEL_1": [0x0100, ["TAxCTL"]],
"TASSEL_3": [0x0300, ["TAxCTL"]],
"TASSEL__ACLK": [0x0100, ["TAxCTL"]],
"TASSEL__INCLK": [0x0300, ["TAxCTL"]],
"CM_0": [0x0000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CM_1": [0x4000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CM_2": [0x8000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CM_3": [0xC000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CM1": [0x8000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CM0": [0x4000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CCIS_0": [0x0000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CCIS_1": [0x1000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CCIS_2": [0x2000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CCIS_3": [0x3000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CCIS1": [0x0800, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CCIS0": [0x1000, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"SCS": [0x0800, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CAP": [0x0100, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CCI": [0x0008, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"COV": [0x0002, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"CCIFG": [0x0001, ["TAxCCTL0", "TAxCCTL1", "TAxCCTL2"]],
"TACLR": [0x0004, ["TAxCTL"]],
"TAIFG": [0x0001, ["TAxCTL"]]
};
// Dies ist die Map für die unterstützten Kontroll-Bits (vgl. C-Header-Datei; Auszug)
const FLAGS = {
TASSEL_0: 0x0000,
TASSEL_2: 0x0200,
TASSEL__TACLK: 0x0000,
TASSEL__SMCLK: 0x0200,
ID__1: 0x0000,
ID__2: 0x0040,
ID__4: 0x0080,
ID__8: 0x00C0,
ID_0: 0x0000,
ID_1: 0x0040,
ID_2: 0x0080,
ID_3: 0x00C0,
MC_0: 0x0000,
MC_1: 0x0010,
MC_2: 0x0020,
MC_3: 0x0030,
MC__STOP: 0x0000,
MC__UP: 0x0010,
MC__CONTINUOUS: 0x0020,
MC__CONTINOUS: 0x0020,
MC__UPDOWN: 0x0030,
TAIE: 0x0002,
OUTMOD_0: 0x0000,
OUTMOD_1: 0x0020,
OUTMOD_2: 0x0040,
OUTMOD_3: 0x0060,
OUTMOD_4: 0x0080,
OUTMOD_5: 0x00A0,
OUTMOD_6: 0x00C0,
OUTMOD_7: 0x00E0,
OUTMOD2: 0x0080,
OUTMOD1: 0x0040,
OUTMOD0: 0x0020,
OUT: 0x0004
};
/*
// ---------------------------------------------------- WERTEANZEIGE ----------------------------------------------------
*/
// Funktion die initial bei Seitenaufruf und bei onchange="" aufgerufen wird
// unterscheidet in "type" (TAxCTL, ...), "source" (slider, text) und standalone (ohne korrespondierende(n) Slider/Texteingabe)
function updateValueDisplay(type, source, standalone) {
//die Quelle gibt an, woher der veränderte Wert stammt. Es darf nicht gleichzeitig vom Benutzer geschrieben und dann von der Software zurpckgeschrieben werden! Das würde unerwünschtes/buggendes Verhalten verursachen
// ist die Quelle ein Textfeld wird diese ausgelesen, sonst der Slider
// das Rückschreiben erfolgt später
if (source == "text") {
decimal = document.getElementById("decimalValue-" + type).value;
} else {
decimal = document.getElementById("slider_" + type).value;
}
// TAxCCTL0 und TAxCCTLn haben einen Sonderstatus, da hier auch Strings (also Kontrollbits) akzeptiert werden
// diese werden an dieser Stelle zunächst gesondert verarbeitet, bevor das Ergebnis weitergegeben wird
if (type == "TAxCCTL0" || type == "TAxCCTL1" || type == "TAxCCTL2" || type == "TAxCTL") {
// getrennt durch '+' (CCIE + OUTMOD_4 +...)
// sum = Summe der einzelnen Bit-Werte, errorParts = unbekannte Eingaben, incompatibleParts = in diesem Eingabefeld nicht unterstützte Eingaben
let sum = 0;
let errorParts = []; // unbekannte Konstanten
let incompatibleParts = []; // bekannte, aber nicht unterstützte
let messages = [];
// Auswerten der Flag-Contanten mit eval
function evalFlags(expr) {
try {
// Ausdruck mit Klassifizierung checken
const ids = expr.match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) || [];
for (const id of ids) {
if (FLAGS.hasOwnProperty(id)) {
// gültig & unterstützt
}
// wenn die Map der nicht untersüttzen Werte den aktuellen typ hält und die Flag definiert ist -> inkompatibel
else if (incompatibleBitValueMap.hasOwnProperty(id) && incompatibleBitValueMap[id][1].includes(type)) {
incompatibleParts.push(id);
} else {
// komplett unbekannt
errorParts.push(id);
}
}
// Wenn Fehler oder inkompatible Werte > KEIN EVAL
if (errorParts.length > 0 || incompatibleParts.length > 0) {
// setze alles 0
return 0;
}
// wenn alles korrekt durchlaufen ist, wird EVAL ausgeführt. Die Fehlerausgabe von Eval zeigt dann an, ob völlig unbekannte Flags verwendet wurdn
return (function() {
with(FLAGS) {
return eval(expr);
}
})();
} catch (err) {
messages.push(err.message + ". Es wird kein Ausganggsignal erstellt.");
// setze alles 0
return 0;
}
}
sum = evalFlags(decimal);
// unbekannte Konstanten
if (errorParts.length > 0) {
messages.push(
`Die Anweisung(en) "${errorParts.join(" & ")}" verstehe ich nicht! Es wird kein Ausganggsignal erstellt.`
);
}
// inkompatible / nicht unterstützte Konstanten
if (incompatibleParts.length > 0) {
messages.push(
`Die Anweisung "${incompatibleParts.join(" & ")}" ist zwar eine existierende Einstellung, wird jedoch in dieser Simulation nicht unterstützt!`
);
}
// WICHTIG: an späterer Stelle wird noch einmal die Eingabe überprüft, jedoch auch die (Hexa)Dezimalen & Binären Eingaben!
// (es wurde erkennt, dass Sie versuchen xy zu verwenden...) -> das geschieht aber GETRENNT hiervon!
// damit die Meldung auch nicht zwei Mal auftaucht, wird der Wert erst gar nicht addiert (return;)
// Ausgabe oder Hinweisbox ausblenden
if (messages.length > 0) {
document.getElementById(`warning-${type}`).style.display = "block";
document.getElementById(`warning-${type}-text`).innerHTML = messages.join("<br>");
} else {
try {
document.getElementById(`warning-${type}`).style.display = "none";
} catch (error) {
console.warn("Zu diesem Wert existiert keine Hinweisbox.");
}
}
// dies ist das Destillat: das hexadezimale Pendant zur "String"-Eingabe
decimal = "0x" + sum.toString(16).toUpperCase().padStart(4, "0");
}
// hier geht es für die Restlichen Werte (also zoom, CCRn...) erst los!
// Credit: Heintz
// Stelle sicher, dass der Wert im Bereich von 0-255 liegt (unteres Byte)
const validValue = decimal & 0xFFFF; // 16-Bit Maske = 65535 dezimal
decimalValueDisplay = validValue.toString().padStart(5, '0'); // bis 65535 (5 Stellen)
hexValueDisplay = '0x' + validValue.toString(16).padStart(4, '0').toUpperCase();
binValueDisplay = '0b' + validValue.toString(2).padStart(16, '0');
// Ende Credit Heintz
// der Wert wird jeweils in drei Stellenwertsystemen simultan ausgegeben, abhängig vom veränderten Eingabefeld
if (type != 'ramp' && type !== 'zoom') {
document.getElementById("bin-hex-dec-display-" + type + "-hex").textContent = hexValueDisplay;
document.getElementById("bin-hex-dec-display-" + type + "-bin").textContent = binValueDisplay;
document.getElementById("bin-hex-dec-display-" + type + "-dec").textContent = decimalValueDisplay;
}
//der Slider-Value (also Select-Option, sliderValue) lässt keine führenden Nullen zu!
decimalValueDisplay = String(Number(decimalValueDisplay));
//schreibe den Wert ins Value
document.getElementById(type).value = decimalValueDisplay;
//schreibe den Wert ins entsprechende Feld
//ausnahme: Beim Body-Onload müssen beide Felder initial beschrieben werden
if (!standalone) {
if (source == "text") {
document.getElementById("slider_" + type).value = decimalValueDisplay;
} else {
if (source == "slider") {
document.getElementById("decimalValue-" + type).value = hexValueDisplay;
} else {
document.getElementById("slider_" + type).value = decimalValueDisplay;
document.getElementById("decimalValue-" + type).value = hexValueDisplay;
}
}
}
// zwei Kleine Funktionen um die Bits zu isolieren und deren Wert zu extrahieren
// genutzt wird Stellenwertverschiebung und Maskierung
function getBit(value, bitIndex) {
return (value >> bitIndex) & 1;
}
function getBits(value, from, to) {
const mask = ((1 << (to - from + 1)) - 1) << from;
return (value & mask) >> from;
}
// etwas unschön: Extraktion der relevanten Bit-Stellen
// diese werden dann in die versteckten HTML-Eingabefelder zurückgeschrieben, um später wieder drauf zuzugreifen
if (type == "TAxCTL") {
document.getElementById("timerMode").value = getBits(validValue, 4, 5);
document.getElementById("ramp").value = getBits(validValue, 6, 7);
document.getElementById("TAIE").checked = getBit(validValue, 1);
document.getElementById("TASSEL").value = getBits(validValue, 8, 9);
}
if (type == "TAxCCTL0") {
document.getElementById("outputMode0").value = getBits(validValue, 5, 7);
document.getElementById("OUT0").checked = getBit(validValue, 2);
}
if (type == "TAxCCTL1") {
document.getElementById("outputMode1").value = getBits(validValue, 5, 7);
document.getElementById("OUT1").checked = getBit(validValue, 2);
}
if (type == "TAxCCTL2") {
document.getElementById("outputMode2").value = getBits(validValue, 5, 7);
document.getElementById("OUT2").checked = getBit(validValue, 2);
}
// hier wird jetzt die gesamte Eingabe (also die (Hexa)Dezimalen & Binären Eingaben) auf Inkompatibilität geprüft
if (type == "TAxCCTL0" || type == "TAxCCTL1" || type == "TAxCCTL2" || type == "TAxCTL") {
// leere wieder die Liste inkompatibler Eingaben
let incompatible = [];
// wurde eine inkompatible Einstellung getroffen?
// part ist z.B. CCIS_3, type ist z.B. TAxCCTL1
// vgl: "CCIS_1": [0x1000, ["TAxCCTL0","TAxCCTL1","TAxCCTL2"]]
// anders als zuvor oben wird zudem sichergestellt, dass der Wert nicht =0 ist.
//Andernfalls würden dauerhaft die Meldungen von z.B. CM_0 oder CCIS_0 auftauchen!
for (const [name, value] of Object.entries(incompatibleBitValueMap)) {
if ((validValue & value[0]) === value[0] && value[0] !== 0 && value[1].includes(type)) {
incompatible.push(name);
}
}
let messages = [];
// Inkompatible Register
if (incompatible.length > 0) {
messages.push(
`Es wurde erkannt, dass Sie versuchen die Bits von "${incompatible.join(" & ")}" anzusprechen. Das ist zwar eine existierende Einstellung, wird jedoch in dieser Simulation nicht unterstützt! Bitte entfernen.`
);
}
// Ausgabe über die Hinweisbox
if (messages.length > 0) {
console.warn(`warning-${type}-1`);
document.getElementById(`warning-${type}-1`).style.display = "block";
document.getElementById(`warning-${type}-1-text`).innerHTML = messages.join("<br>");
} else {
try {
document.getElementById(`warning-${type}-1`).style.display = "none";
} catch (error) {
console.warn("Zu diesem Wert existiert keine Hinweisbox.");
}
}
}
//bei verändertem Wert -> Canvas neu zeichnen
drawCanvas();
}
// die Divider-Map definiert den Faktor, mit welchem die Anzeige skaliert wird (also Breite der Anzeige = Kehrwert der Zweierpotenz)
const dividerMap = {
"0": 1,
"1": 2,
"2": 4,
"3": 8
};
/*
// ---------------------------------------------------- BEGINN: ZEICHNEN ----------------------------------------------------
*/
function drawCanvas() {
// Eingabewert holen
const zoom = parseInt(document.getElementById("zoom").value);
const rampOriginal = dividerMap[document.getElementById("ramp").value];
const ramp = rampOriginal * (zoom / 100);
// Anzeige für die Zoomstufe
document.getElementById("zoom-display-perc").textContent = (zoom).toFixed(0) + "%";
document.getElementById("zoom-display-rel").textContent = "1:" + (zoom / 100).toFixed(2);
// Simulationsrelevante Werte holen
const timerMode = parseInt(document.getElementById("timerMode").value); // Hole Timer-Mode aus Select-Feld
const outputMode0 = parseInt(document.getElementById("outputMode0").value); // Hole Output-Mode aus Select-Feld
const outputMode1 = parseInt(document.getElementById("outputMode1").value); // Hole Output-Mode aus Select-Feld
const outputMode2 = parseInt(document.getElementById("outputMode2").value); // Hole Output-Mode aus Select-Feld
const TAIE = document.getElementById("TAIE").checked; // Hole Startwert aus Select-Feld
const TASSEL = parseInt(document.getElementById("TASSEL").value); // Hole Startwert aus Select-Feld
const OUT0 = document.getElementById("OUT0").checked;
const OUT1 = document.getElementById("OUT1").checked;
const OUT2 = document.getElementById("OUT2").checked;
let TAxCCR0_val = parseInt(document.getElementById("TAxCCR0").value);
let TAxCCR1_val = parseInt(document.getElementById("TAxCCR1").value);
let TAxCCR2_val = parseInt(document.getElementById("TAxCCR2").value);
const startValue = document.getElementById("startValue").checked;
console.log(startValue);
// die filterList ist eine übergreifende Einstellung und beschreibt, welche Schnittpunkte verarbeitet werden sollen
// die Schnittpunkte werden zwar alle gespichert, an späterer Stelle aber nicht ausgegeben!
const filterList = [];
// TAIE aktiviert TAIFG
if (TAIE) {
filterList.push("TAIFG");
}
// EQU 0-2 ist immer aktiv
filterList.push("TAxCCR0");
filterList.push("TAxCCR1");
filterList.push("TAxCCR2")
// hier geht's los mit dem Zeichnen des Canvas!
// Canvas löschen
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Haupt-Einstellungen
ctx.beginPath();
ctx.setLineDash([]);
ctx.font = "20px Roboto Mono";
ctx.textAlign = "right";
ctx.textBaseline = "bottom";
if (TAxCCR0_val <= TAxCCR1_val && timerMode !== 2) { // Fallback, wenn TAxCCR0 >= TAxCCR1 ist
//TAxCCR1_val = TAxCCR0_val - 1;
ctx.fillText("TAR wird TAxCCR1 niemals erreichen!", 700, 30); // Hinweisbox
}
if (TAxCCR0_val <= TAxCCR2_val && timerMode !== 2) { // Fallback, wenn TAxCCR0 >= TAxCCR1 ist
//TAxCCR2_val = TAxCCR1_val - 1;
ctx.fillText("TAR wird TAxCCR2 niemals erreichen!", 700, 50); // Hinweisbox
}
// Normierung des 16-Bit-Werts auf die 200px-Höhe des Wert-Zeit-Diagramms des Timer_A
const TAxCCR0_pos = ((65536 - TAxCCR0_val) / 65536) * 200;
const TAxCCR1_pos = ((65536 - TAxCCR1_val) / 65536) * 200;
const TAxCCR2_pos = ((65536 - TAxCCR2_val) / 65536) * 200;
graphBoxMarginLeft = 110; // Margin bis zu linkten Canvas-Begrenzung (Text und Achse orienitert sich hieran)
graphBoxMarginTop = 10; // Margin bis zu oberen Canvas-Begrenzung (Text und Achse orienitert sich hieran)
// Array, das die Schnittpunkte erfasst
graphIntersections = [];
// Das sind die Grundlinien des Koordinatensystems ("Box")
ctx.moveTo(graphBoxMarginLeft + 0, graphBoxMarginTop + 0);
ctx.lineTo(graphBoxMarginLeft + 0, graphBoxMarginTop + 200);
ctx.moveTo(graphBoxMarginLeft + 0, graphBoxMarginTop + 200);
ctx.lineTo(graphBoxMarginLeft + 800, graphBoxMarginTop + 200);
ctx.stroke();
/*
// ---------------------------------------------------- ZEICHNEN DER LIMITS ----------------------------------------------------
*/
// Textbeschriftungen
ctx.fillText("0h", graphBoxMarginLeft - 10, graphBoxMarginTop + 200);
ctx.textBaseline = "top";
ctx.fillText("0FFFFh", graphBoxMarginLeft - 10, graphBoxMarginTop);
if (true) { // CCR0 wird immer gezeichnet
// gestrichelte Linie zeichnen, je nach TAxCCR0
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.moveTo(graphBoxMarginLeft, graphBoxMarginTop + TAxCCR0_pos);
ctx.lineTo(graphBoxMarginLeft + 800, graphBoxMarginTop + TAxCCR0_pos);
ctx.stroke();
// Text zur gestrichelten Linie TAxCCR0
ctx.textBaseline = "middle";
ctx.fillText("TAxCCR0", graphBoxMarginLeft - 10, graphBoxMarginTop + TAxCCR0_pos);
}
if (true) {
// gestrichelte Linie zeichnen, je nach TAxCCR1
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.moveTo(graphBoxMarginLeft, graphBoxMarginTop + TAxCCR1_pos);
ctx.lineTo(graphBoxMarginLeft + 800, graphBoxMarginTop + TAxCCR1_pos);
ctx.stroke();
// Text zur gestrichelten Linie TAxCCR1
ctx.textBaseline = "middle";
ctx.fillText("TAxCCR1", graphBoxMarginLeft - 10, graphBoxMarginTop + TAxCCR1_pos);
}
if (true) {
// gestrichelte Linie zeichnen, je nach TAxCCR2
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.moveTo(graphBoxMarginLeft, graphBoxMarginTop + TAxCCR2_pos);
ctx.lineTo(graphBoxMarginLeft + 800, graphBoxMarginTop + TAxCCR2_pos);
ctx.stroke();
// Text zur gestrichelten Linie TAxCCR1
ctx.textBaseline = "middle";
ctx.fillText("TAxCCR2", graphBoxMarginLeft - 10, graphBoxMarginTop + TAxCCR2_pos);
}
/*
// ---------------------------------------------------- INITIALISIERUNG DER SCHLEIFE ----------------------------------------------------
*/
drawnWidth = 0; // gibt die Breite des gezeichneten Graphen an - neue Elemente knüpfen hier an!
drawnWidthTAxCCR0 = 0; // Extra-Breiten-Zähler, wird relevant später, entspricht in Modus 0 und 2 drawnWidth
drawnWidthTAxCCR1 = 0; // Extra-Breiten-Zähler, wird relevant später
drawnWidthTAxCCR2 = 0; // Extra-Breiten-Zähler, wird relevant später
limiter = 0; // begrenzt die maximale Anzahl gezeichneter Elemente (Fallback, falls Drawn-Width nie erreicht wird und der Browser abstürzt)
timer = 0;
//die targetHeight ist im Cont. Mode 200-200=0 (Abstand vom Oberen Ende der X-Achse!)
if (timerMode == 2) {
targetHeight = 0;
} else {
targetHeight = TAxCCR0_pos;
}
/*
// ---------------------------------------------------- BEGINN DER SCHLEIFE ----------------------------------------------------
*/
// Haupt-Schleife: Will erreichen, dass Graph mind. 1400 Pixel breit; Limiter begrenzt, dass Browser nicht abstürzt; timer = halted = keine Zeichnung
while (drawnWidth < 1600 && limiter <= 100 && timerMode !== 0) {
ctx.beginPath();
ctx.setLineDash([]); // normale Linien
ctx.strokeStyle = colors["Blue 3"];
ctx.lineWidth = 3;
// Schräge Linie (Rampe) Breite: Von 0 bis (200 - targetHeight) * ramp
ctx.moveTo(graphBoxMarginLeft + drawnWidth, 200 + graphBoxMarginTop);
// Schnittpunkt mit 0h ist schonmal ein Tal
graphIntersections.push({
"x": drawnWidth,
"y": (200 - targetHeight),
"limitType": "TAIFG",
"edgeType": "through"
});
// | Zielbreite | X-Pos des TAxCCRn-Schnittpunkts
drawnWidthTAxCCR1 = drawnWidth + (200 - TAxCCR1_pos) * ramp;
drawnWidthTAxCCR2 = drawnWidth + (200 - TAxCCR2_pos) * ramp;
drawnWidthTAxCCR0 = drawnWidth + (200 - TAxCCR0_pos) * ramp;
// Zielbreite für dieses Segment - in Modus 2 doppelt so breit! (Addition später)
drawnWidth = drawnWidth + (200 - targetHeight) * ramp;
ctx.lineTo(graphBoxMarginLeft + drawnWidth, graphBoxMarginTop + targetHeight);
// Im Timer Mode 2 wird eine fallende Rampe statt eines geraden Abschlusses benötigt
if (timerMode == 3) {
// die Zielbreite ist hier doppelt so groß, da 2*Rampe
// | Zweite Rampe |
ctx.moveTo(graphBoxMarginLeft + drawnWidth + (200 - targetHeight) * ramp, 200 + graphBoxMarginTop);
ctx.lineTo(graphBoxMarginLeft + drawnWidth, graphBoxMarginTop + targetHeight);
// Schnittpunkt mit TAxCCR0 bei Berühren
graphIntersections.push({
"x": drawnWidth,
"y": TAxCCR0_pos,
"limitType": "TAxCCR0",
"edgeType": "peak"
});
// Finales Aufaddieren der tatsächlichen Größe (also 2*Rampe)
drawnWidth = drawnWidth + (200 - targetHeight) * ramp;
// Schnittpunkt mit TAxCCR1 (kann von TAxCCR1_pos abgezwackt werden)
if (targetHeight < TAxCCR1_pos) {
graphIntersections.push({
"x": drawnWidthTAxCCR1,
"y": TAxCCR1_pos,
"limitType": "TAxCCR1",
"edgeType": "rise"
});
}
// Schnittpunkt mit TAxCCR2 (kann von TAxCCR2_pos abgezwackt werden)
if (targetHeight < TAxCCR2_pos) {
graphIntersections.push({
"x": drawnWidthTAxCCR2,
"y": TAxCCR2_pos,
"limitType": "TAxCCR2",
"edgeType": "rise"
});
}
// für die fallende Taktflanke im Modus 2 wird sich die Symmetrie zu Nutze gemacht:
// auf die gezeichnete Breite (also Scheitel des Dreiecks) wird die Differenz aus TAxCCR0 und TAxCCR1 addiert! -> Betrag zum Scheitel (Quasi Spiegelung -> Fall)
if (targetHeight < TAxCCR1_pos) {
graphIntersections.push({
"x": drawnWidth - (200 - TAxCCR1_pos) * ramp,
"y": TAxCCR1_pos,
"limitType": "TAxCCR1",
"edgeType": "fall"
});
}
if (targetHeight < TAxCCR2_pos) {
graphIntersections.push({
"x": drawnWidth - (200 - TAxCCR2_pos) * ramp,
"y": TAxCCR2_pos,
"limitType": "TAxCCR2",
"edgeType": "fall"
});
}
} else { // Wenn ein gerader Abschluss benötigt wird
// gerader Abschluss mit der Höhe targetHeight
ctx.moveTo(graphBoxMarginLeft + drawnWidth, 200 + graphBoxMarginTop);
ctx.lineTo(graphBoxMarginLeft + drawnWidth, graphBoxMarginTop + targetHeight);
// Schnittpunkt (Peak) mit TAxCCR0 bei steigender Flanke (X-Pos ist die gezeichnete Breite, da TAxCCR0 die Breite vorgibt), ist drawnWidthTAxCCR0 != drawnWidth, handelt es sich nicht um den gleichen Punkt, sondern der Graph ist noch ein Bewegung -> Rise
if (drawnWidthTAxCCR0 == drawnWidth) {
graphIntersections.push({
"x": drawnWidthTAxCCR0,
"y": TAxCCR0_pos,
"limitType": "TAxCCR0",
"edgeType": "peak"
});
} else {
graphIntersections.push({
"x": drawnWidthTAxCCR0,
"y": TAxCCR0_pos,
"limitType": "TAxCCR0",
"edgeType": "rise"
});
}
// Schnittpunkt mit TAxCCR1 bei steigender Flanke (X-Pos ist die gezeichnete Breite)
if (targetHeight < TAxCCR1_pos) {
graphIntersections.push({
"x": drawnWidthTAxCCR1,
"y": TAxCCR1_pos,
"limitType": "TAxCCR1",
"edgeType": "rise"
});
}
if (targetHeight < TAxCCR2_pos) {
graphIntersections.push({
"x": drawnWidthTAxCCR2,
"y": TAxCCR2_pos,
"limitType": "TAxCCR2",
"edgeType": "rise"
});
}
}
ctx.stroke();
ctx.strokeStyle = colors["Dark 5"];
ctx.lineWidth = 1;
limiter++;
}
/*
// ---------------------------------------------------- ENDE DER SCHLEIFE ----------------------------------------------------
*/
// sicherheitshalber das Array nach X-Koordinate sortieren
graphIntersections.sort(function(a, b) {
return a.x - b.x
});
/*
// ---------------------------------------------------- X-ACHSENBESCHRIFTUNG ----------------------------------------------------
*/
ctx.textAlign = "center";
ctx.font = "15px Roboto Mono";
x_before = -200;
graphIntersections.forEach(element => {
//legacy: nur TAIFG als Orientierung nutzen
//if (element["limitType"] === "TAxCCR2") {
t = (element["x"] / 3.051757812 / (zoom * 0.01)).toFixed(2);
if ((element["x"] - x_before) > 100) {
ctx.fillText(t + "ms", graphBoxMarginLeft + element["x"], graphBoxMarginTop + 225);
ctx.setLineDash([]); // normale Linien
ctx.beginPath();
ctx.moveTo(graphBoxMarginLeft + element["x"], graphBoxMarginTop + 210);
ctx.lineTo(graphBoxMarginLeft + element["x"], graphBoxMarginTop + 200);
ctx.stroke();
x_before = element["x"];
}
});
ctx.font = "20px Roboto Mono";
// die zuvor festgelegte Fiterliste wird nun auf die ermittelten Schnittpunkte angewandt
// ist bspw. für TAIFG gar kein TAIE gesetzt, fliegt es hier nun raus
graphIntersections = graphIntersections.filter(item => filterList.includes(item.limitType));
/*
// --------------------------------------- ZEICHNEN DER SCHNITTPUNKTE/INTERRUPTS --------------------------------------------
*/
// Parameter für jeden Interrupt-Kanal
const drawParameters = [{
type: "TAIFG",
label: "TAIFG",
startY: 310
},
{
type: "TAxCCR0",
label: "EQU0",
startY: 410,
endY: 960,
dashed: true
},
{
type: "TAxCCR1",
label: "EQU1",
startY: 510,
endY: 960,
dashed: true
},
{
type: "TAxCCR2",
label: "EQU2",
startY: 610,
endY: 960,
dashed: true
}
];
graphBoxMarginLeft = 110;
const boxHeight = 50;
// Textausrichtung für alle Boxen zentral setzen
ctx.textAlign = "right";
ctx.textBaseline = "bottom";
ctx.font = "20px Roboto Mono";
// --- START DER DEDUPLIZIERENDEN SCHLEIFE ---
for (let j = 0; j < drawParameters.length; j++) {
const params = drawParameters[j];
// 1. Grundlinien (Achsen) zeichnen
ctx.beginPath();
ctx.setLineDash([]); // Normale Linien (Achsen)
ctx.moveTo(graphBoxMarginLeft, params.startY);
ctx.lineTo(graphBoxMarginLeft, params.startY + boxHeight); // Y-Achse
ctx.moveTo(graphBoxMarginLeft, params.startY + boxHeight);
ctx.lineTo(graphBoxMarginLeft + 800, params.startY + boxHeight); // X-Achse
ctx.stroke();
// 2. Textbeschriftung links (TAIFG, EQU0, EQU1, EQU2)
ctx.fillText(params.label, graphBoxMarginLeft - 10, params.startY + boxHeight);
// 3. Schnittpunkte verarbeiten und zeichnen
graphIntersections.forEach(element => {
if (element["limitType"] === params.type) {
// Schnittpunktlinie (V-Linie)
ctx.beginPath();
ctx.setLineDash([]); // Normale Linie
ctx.strokeStyle = colors["Blue 3"];
ctx.lineWidth = 3;
const xPos = element["x"] + graphBoxMarginLeft;
ctx.moveTo(xPos, boxHeight + params.startY);
ctx.lineTo(xPos, params.startY);
// Beschriftung des Schnittpunkts (edgeType)
ctx.fillText(element["edgeType"], xPos, params.startY);
ctx.stroke();
ctx.strokeStyle = colors["Dark 5"];
ctx.lineWidth = 1;
// 4. Gestrichelte Linien zur Orientierung (nur für CCRn)
if (params.dashed) {
ctx.setLineDash([5, 15]); // Gestrichelte Linie
ctx.strokeStyle = colors["Dark 1"];
ctx.beginPath();
ctx.moveTo(xPos, graphBoxMarginTop + element["y"]);
ctx.lineTo(xPos, params.endY); // Endpunkt ist variabel (endY)
ctx.stroke();
ctx.strokeStyle = colors["Dark 5"];
}
}
});
}
/*
// --------------------------------------- DEFINITION DES OUTPUT-PATTERN --------------------------------------------
*/
// das Outmode-Pattern kann fixe Werte, Toggle sowie Set/Reset speichern
// outputModePatterns[i] beschreibt immer den verwendeten Timer-Mode
// outputModePatterns[i]["default"/"up-down"] unterscheidet in up/down oder cont/up weil z.b. im Output Mode 3: Set/Reset im Up/down bei ersterem Erreichen des Limits kein Interrupt ausgelöst wird
// outputModePatterns[i]["default"/"up-down"]["TAxCCR0"/"TAxCCRn"] unterscheidet zwischen CCR0 und CCRn
// WICHTIG: CCR0 gilt als CCR0 und CCRn gleichzeitig!
// outputModePatterns[i]["default"/"up-down"]["TAxCCR0"/"TAxCCRn"]["through"/"rise"/"peak"/"fall"] unterscheidet zwischen den "Flanken-Typen"
// outputModePatterns[i]["default"/"up-down"]["TAxCCR0"/"TAxCCRn"]["through"/"rise"/"peak"/"fall"][0/1/2] setzt die tatsächlichen Werte!
// [0] steht für "Wert ist aktuell false" und [1] steht für "Wert ist aktuell true"
// ein Toggle lässt sich also über [true, false] realisiern, ein bedinungsloser Reset durch [false, false] usw..
// WICHTIG!: [2] beschreibt einen fixen Werrt, der bei Set und Reset zum Tragen kommt. Hier wird angegeben, wie sich die Simulation bei Erreichen des Wertes dann verhalten soll
// Grafiken nachgebaut (beschreibt die Hardware falsch, da Dokumentationsfehler!)
/*
outputModePatterns = {
0: // 1
{
"default": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
}
}
},
1: // 1
{
"default": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true]
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true]
}
}
},
2: // 2
{
"default": {
"TAxCCR0": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": true
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false],
"bistable": true
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true],
"bistable": true
},
"TAxCCRn": {
"rise": [true, false],
"peak": [false, true],
"fall": [true, true],
"bistable": true
}
}
},
3: // 3
{
"default": {
"TAxCCR0": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": true
},
"TAxCCRn": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": true
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": true
},
"TAxCCRn": {
"rise": [false, true],
"peak": [false, true],
"fall": [true, true],
"bistable": true
}
}
},
4: // 4
{
"default": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false]
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false]
}
}
},
5: // 5
{
"default": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false]
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false]
}
}
},
6: // 6
{
"default": {
"TAxCCR0": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": false
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false],
"bistable": false
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true],
"bistable": false
},
"TAxCCRn": {
"rise": [true, false],
"peak": [false, true],
"fall": [false, false],
"bistable": false
}
}
},
7: // 7
{
"default": {
"TAxCCR0": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": false
},
"TAxCCRn": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": false
}
},
"up-down": {
"TAxCCR0": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": false
},
"TAxCCRn": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, false],
"bistable": false
}
}
},
};
// am Text/der Tabelle Orientiert (so korrekt nachgemessen)
*/
outputModePatterns = {
0: // 1
{
"default": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
}
}
},
1: // 1
{
"default": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true]
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true]
}
}
},
2: // 2
{
"default": {
"TAxCCR0": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": true
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false],
"bistable": true
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": true
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false],
"bistable": true
}
}
},
3: // 3
{
"default": {
"TAxCCR0": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": true
},
"TAxCCRn": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": true
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": true
},
"TAxCCRn": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": true
}
}
},
4: // 4
{
"default": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false]
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false]
}
}
},
5: // 5
{
"default": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false]
}
},
"up-down": {
"TAxCCR0": {
"rise": [false, true],
"peak": [false, true],
"fall": [false, true]
},
"TAxCCRn": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false]
}
}
},
6: // 6
{
"default": {
"TAxCCR0": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": false
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false],
"bistable": false
}
},
"up-down": {
"TAxCCR0": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": false
},
"TAxCCRn": {
"rise": [true, false],
"peak": [true, false],
"fall": [true, false],
"bistable": false
}
}
},
7: // 7
{
"default": {
"TAxCCR0": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": false
},
"TAxCCRn": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": false
}
},
"up-down": {
"TAxCCR0": {
"rise": [true, true],
"peak": [true, true],
"fall": [true, true],
"bistable": false
},
"TAxCCRn": {
"rise": [false, false],
"peak": [false, false],
"fall": [false, false],
"bistable": false
}
}
},
};
/*
// --------------------------------------- SCHNITTPUNKTE -> OUTPUT-LEVELS --------------------------------------------
*/
// aus den Zuvor ermittelten Schnittpunkten wird nun unter Zuhilfenahme der Modus-Map das Output-Level erstellt
// Hier wird outputLevels als Array von Arrays initialisiert,
// um outputLevels[0], outputLevels[1], outputLevels[2] zu haben
// dieser Speichert den zeitlichen Verlauf des Ausgabegraphen
const outputLevels = [
[], // Index 0: Inhalt von outputLevel0
[], // Index 1: Inhalt von outputLevel1
[], // Index 2: Inhalt von outputLevel2
];
// im OutMode0 (also OUT) wird nur ein fixer Wert gegeben, der mit dem OUT-Bit festgelegt wird
startValue0 = startValue;
if (outputMode0 == 0) {
startValue0 = OUT0;
}
startValue1 = startValue;
if (outputMode1 == 0) {
startValue1 = OUT1;
}
startValue2 = startValue;
if (outputMode2 == 0) {
startValue2 = OUT2;
}
// Zustandsspeicher für jede Iteration jedes Kanals
// früher wurden die Werte für jeden Kanal einzeln gespeichert (also toggle0, toggle1..., i0, i1...) das ist hiermit schöner gelöst
const outputStates = {
0: {
toggle: startValue0,
x_from: 0,
i: 0
},
1: {
toggle: startValue1,
x_from: 0,
i: 0
},
2: {
toggle: startValue2,
x_from: 0,
i: 0
}
};
// diese Funktion verwendet das Pattern um den Output-Verlauf zu erstellen
// es wird durch die Iteration durch alle Schnittpunkte aufgerufen
function useOutputModePattern(outputIndex, element, mode, timerMode, enableBistable) {
// Hole den aktuellen Zustand für diesen Output-Kanal
const state = outputStates[outputIndex];
// Bestimme den Output-Case
// im Up/down-Mode gibts die Besonderheit, dass teilweise bei fallenden Flanken kein Interrupt mehr ausgelöst wird,
// bzw. die Symmetrie absichtlich verletzt wird
const outputCase = (timerMode === 3) ? "up-down" : "default";
// Hole das Pattern-Array
const pattern = outputModePatterns[mode][outputCase][element["limitType"]]?.[element["edgeType"]];
if (!pattern) return; // Abbruch, wenn das Pattern nicht existiert
const x_to = element["x"];
// schreibe den Wert nun in das ouputLevel-Array
outputLevels[outputIndex].push({
"x_from": state.x_from,
"x_to": x_to,
"level": state.toggle
});
// Werte setzen für die nächste Iteration
state.x_from = x_to;
if (outputModePatterns[mode][outputCase][element["limitType"]]["bistable"] !== undefined && enableBistable) {
state.toggle = outputModePatterns[mode][outputCase][element["limitType"]]["bistable"];
} else {
state.toggle = state.toggle ? pattern[1] : pattern[0];
}
}
console.log(graphIntersections);
// iteriere durh alle Schnittpunkte
// wichtige Besonderheit bei CCR0: dieser wird sowohl als CCR0, als auch CCRn behandelt! CCR0 ist jedoch dominant
graphIntersections.forEach(element => {
// Speichere den Original-limitType, da er von der Logik geändert wird
const originalLimitType = element["limitType"];
// 1. Logik für Output 0 (CCR0 als CCRn)
if (originalLimitType === "TAxCCR0") {
// Durchlauf (als CCRn mit Nachrang)
element["limitType"] = "TAxCCRn";
useOutputModePattern(0, element, outputMode0, timerMode, true);
}
// 2. Logik für Output 1 (TAxCCR0 oder TAxCCR1)
if (["TAxCCR0", "TAxCCR1"].includes(originalLimitType)) {
if (originalLimitType === "TAxCCR1") {
element["limitType"] = "TAxCCRn";
} else {
element["limitType"] = originalLimitType; // TAxCCR0
}
useOutputModePattern(1, element, outputMode1, timerMode);
}
// 3. Logik für Output 2 (TAxCCR0 oder TAxCCR2)
if (["TAxCCR0", "TAxCCR2"].includes(originalLimitType)) {
if (originalLimitType === "TAxCCR2") {
element["limitType"] = "TAxCCRn";
} else {
element["limitType"] = originalLimitType; // TAxCCR0
}
useOutputModePattern(2, element, outputMode2, timerMode);
}
// Stelle den limitType wieder her, bevor die nächste Iteratiion der Schleife startet
element["limitType"] = originalLimitType;
});
/*
// --------------------------------------- AUSGABE DER OUTPUT-LEVELS --------------------------------------------
*/
// Parameter für die Anzeigeboxen
const outputDrawParameters = [{
label: "Output0",
startY: 710,
index: 0
},
{
label: "Output1",
startY: 810,
index: 1
},
{
label: "Output2",
startY: 910,
index: 2
}
];
graphBoxMarginLeft = 110;
// Einmaliges Setzen des Text-Kontextes
ctx.textAlign = "right";
ctx.textBaseline = "bottom";
ctx.font = "20px Roboto Mono";
// Iteriere durch alle zu zeichnenden Elemente
outputDrawParameters.forEach(params => {
// Lokale Variablen für diese Box
const graphBoxMarginTop = params.startY;
let level_prev = undefined; // Zustand (level_prev) wird für jeden Output separat initialisiert
// Zeichnet ein einzelnes Level und wird augerufen, wenn eine Linie (x_from -> x_to) gezeichnet werden soll
function drawOutput(element) {
ctx.beginPath();
ctx.setLineDash([]); // Normale Linien (für den Output-Verlauf)
ctx.strokeStyle = colors["Blue 3"];
ctx.lineWidth = 3;
// Horizontaler Verlauf
if (element["level"]) {
// HIGH Level
ctx.moveTo(element["x_from"] + graphBoxMarginLeft, graphBoxMarginTop);
ctx.lineTo(element["x_to"] + graphBoxMarginLeft, graphBoxMarginTop);
} else {
// LOW Level
ctx.moveTo(element["x_from"] + graphBoxMarginLeft, boxHeight + graphBoxMarginTop);
ctx.lineTo(element["x_to"] + graphBoxMarginLeft, boxHeight + graphBoxMarginTop);
}
// Vertikale Flanke (wenn sich der Pegel ändert)
if ((level_prev !== element["level"]) && level_prev !== undefined) {
ctx.moveTo(element["x_from"] + graphBoxMarginLeft, boxHeight + graphBoxMarginTop);
ctx.lineTo(element["x_from"] + graphBoxMarginLeft, graphBoxMarginTop);
}
// Zustand für die nächste Iteration speichern
level_prev = element["level"];
ctx.stroke();
ctx.strokeStyle = colors["Dark 5"];
ctx.lineWidth = 1;
}
// das eigentliche Zeichnen der Box
// 1. Grundlinien (Achsen) zeichnen
ctx.beginPath();
ctx.setLineDash([5, 5]); // Gestrichelte Linien für die Grundlinie (wie im Original)
ctx.moveTo(graphBoxMarginLeft, graphBoxMarginTop);
ctx.lineTo(graphBoxMarginLeft, graphBoxMarginTop + boxHeight); // Y-Achse
ctx.moveTo(graphBoxMarginLeft, graphBoxMarginTop + boxHeight);
ctx.lineTo(graphBoxMarginLeft + 800, graphBoxMarginTop + boxHeight); // X-Achse
ctx.stroke();
ctx.setLineDash([]); // Gestrichelte Linien wieder deaktivieren
// 2. Textbeschriftung links (Output0, Output1, Output2)
ctx.fillText(params.label, graphBoxMarginLeft - 10, graphBoxMarginTop + boxHeight);
// 3. Output-Segmente zeichnen mit der definierten Funktion
outputLevels[params.index].forEach(element => {
drawOutput(element);
});
});
console.log(outputLevels);
// ende des Canvas! lösche alles, was breiter als 900px nach rechts hinausläuft
ctx.clearRect(900, 0, canvas.width, canvas.height);
/*
// --------------------------------------- TEXT-ERKLÄRUNG --------------------------------------------
*/
// Dies sind die Timer/Output-Mode in Worten
const timerModeMap = {
0: "Hold",
1: "Up",
2: "Continuous",
3: "Up-/Down"
};
const outputModeMap = {
0: "Output",
1: "Set",
2: "Toggle-/Reset",
3: "Set-/Reset",
4: "Toggle",
5: "Reset",
6: "Toggle-/Set",
7: "Reset-/Set"
};
const tasselMap = {
0: "TAxCLK (1 MHz)",
1: "ACLK",
2: "SMCLK (32 kHz)",
3: "INCLK"
};
// für jede dieser Werte gibts eine Erlärung, die zunächst gesammelt wird
explanatoryText = {
"TAxCTL": "",
"TAxCCTL0": "",
"TAxCCTL1": "",
"TAxCCTL2": ""
};
// Erklärung des Timers
explanatoryText["TAxCTL"] += "Der Timer befindet sich im <b>" + timerModeMap[timerMode] + "-Mode</b> mit einem </b>Teilerwert von <b>1/" + rampOriginal + "</b>. Das Timer_A-Interrupt-Enable (<b>TAIE</b>) ist " + TAIE + ". Timer-Quelle: <b>" + tasselMap[TASSEL] + "</b>.";
// Timer-Mode mit optionalem OUTn-Wert
explanatoryText["TAxCCTL0"] += "CCTL0 befindet sich im <b>" + outputModeMap[outputMode0] + "-Modus</b> mit dem OUT-Wert <b>" + OUT0 + "</b>.";
if (outputModePatterns[outputMode0]["default"]["TAxCCRn"]["bistable"] !== undefined) {
explanatoryText["TAxCCTL0"] += "<br><b>Sie haben einen Sonderfall entdeckt! Weil CCRn == CCR0, erzeugt der OUT0-Kanal in diesem Output-Modus ein unerwartetes Signal...</b>";
}
explanatoryText["TAxCCTL1"] += "CCTL1 befindet sich im <b>" + outputModeMap[outputMode1] + "-Modus</b> mit dem OUT-Wert <b>" + OUT1 + "</b>.";
explanatoryText["TAxCCTL2"] += "CCTL2 befindet sich im <b>" + outputModeMap[outputMode2] + "-Modus</b> mit dem OUT-Wert <b>" + OUT2 + "</b>.";
// schreibe die Erklärungen in die zugehörige Box
Object.entries(explanatoryText).forEach(([key, value]) => {
document.getElementById("explain-" + key + "-text").innerHTML = value;
});
}
</script>