aboutsummaryrefslogtreecommitdiff
path: root/demo/src
diff options
context:
space:
mode:
Diffstat (limited to 'demo/src')
-rw-r--r--demo/src/compile.ts20
-rw-r--r--demo/src/execute.ts38
-rw-r--r--demo/src/format-time.ts14
-rw-r--r--demo/src/index.css48
-rw-r--r--demo/src/index.html28
-rw-r--r--demo/src/index.ts105
-rw-r--r--demo/src/intelhex.ts18
-rw-r--r--demo/src/led.ts102
8 files changed, 373 insertions, 0 deletions
diff --git a/demo/src/compile.ts b/demo/src/compile.ts
new file mode 100644
index 0000000..4fca6c6
--- /dev/null
+++ b/demo/src/compile.ts
@@ -0,0 +1,20 @@
+const url = 'https://wokwi-hexi-73miufol2q-uc.a.run.app';
+
+export interface IHexiResult {
+ stdout: string;
+ stderr: string;
+ hex: string;
+}
+
+export async function buildHex(source: string) {
+ const resp = await fetch(url + '/build', {
+ method: 'POST',
+ mode: 'cors',
+ cache: 'no-cache',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ sketch: source })
+ });
+ return (await resp.json()) as IHexiResult;
+}
diff --git a/demo/src/execute.ts b/demo/src/execute.ts
new file mode 100644
index 0000000..96a0411
--- /dev/null
+++ b/demo/src/execute.ts
@@ -0,0 +1,38 @@
+import { avrInstruction, AVRTimer, CPU, timer0Config } from 'avr8js';
+import { loadHex } from './intelhex';
+
+// ATmega328p params
+const FLASH = 0x8000;
+
+export class AVRRunner {
+ readonly program = new Uint16Array(FLASH);
+ readonly cpu: CPU;
+ readonly timer: AVRTimer;
+
+ private stopped = false;
+
+ constructor(hex: string) {
+ loadHex(hex, new Uint8Array(this.program.buffer));
+ this.cpu = new CPU(this.program);
+ this.timer = new AVRTimer(this.cpu, timer0Config);
+ }
+
+ async execute(callback: (cpu: CPU) => void) {
+ this.stopped = false;
+ for (;;) {
+ avrInstruction(this.cpu);
+ this.timer.tick();
+ if (this.cpu.cycles % 50000 === 0) {
+ callback(this.cpu);
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ if (this.stopped) {
+ break;
+ }
+ }
+ }
+ }
+
+ stop() {
+ this.stopped = true;
+ }
+}
diff --git a/demo/src/format-time.ts b/demo/src/format-time.ts
new file mode 100644
index 0000000..a82b3b0
--- /dev/null
+++ b/demo/src/format-time.ts
@@ -0,0 +1,14 @@
+function zeroPad(value: number, length: number) {
+ let sval = value.toString();
+ while (sval.length < length) {
+ sval = '0' + sval;
+ }
+ return sval;
+}
+
+export function formatTime(seconds: number) {
+ const ms = Math.floor(seconds * 1000) % 1000;
+ const secs = Math.floor(seconds % 60);
+ const mins = Math.floor(seconds / 60);
+ return `${zeroPad(mins, 2)}:${zeroPad(secs, 2)}.${zeroPad(ms, 3)}`;
+}
diff --git a/demo/src/index.css b/demo/src/index.css
new file mode 100644
index 0000000..a3fa8b8
--- /dev/null
+++ b/demo/src/index.css
@@ -0,0 +1,48 @@
+body {
+ padding: 0 16px;
+ font-family: 'Roboto', sans-serif;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.app-container {
+ width: 500px;
+ max-width: 100%;
+}
+
+.toolbar {
+ padding: 4px;
+ display: flex;
+ background-color: #ddd;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.toolbar > button {
+ margin-right: 4px;
+}
+
+.spacer {
+ flex: 1;
+}
+
+.code-editor {
+ width: 100%;
+ max-width: 100%;
+ height: 300px;
+ box-sizing: border-box;
+ border: 1px solid grey;
+}
+
+.compiler-output {
+ width: 500px;
+ box-sizing: border-box;
+ padding: 8px 12px;
+ max-height: 120px;
+ overflow: auto;
+}
+
+.compiler-output pre {
+ margin: 0;
+ white-space: pre-line;
+}
diff --git a/demo/src/index.html b/demo/src/index.html
new file mode 100644
index 0000000..935bcff
--- /dev/null
+++ b/demo/src/index.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+ <title>AVR8js LED Demo</title>
+ <link href="//fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet" />
+ </head>
+ <body>
+ <h2>AVR8js LED Demo</h2>
+ <div class="app-container">
+ <div class="leds"></div>
+ <div class="toolbar">
+ <button id="run-button">Run</button>
+ <button id="stop-button" disabled>Stop</button>
+ <div class="spacer"></div>
+ <div id="status-label"></div>
+ </div>
+ <div class="code-editor"></div>
+ <div class="compiler-output">
+ <pre id="compiler-output-text"></pre>
+ </div>
+ </div>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.18.0/min/vs/loader.js"></script>
+ <script src="./index.ts"></script>
+ </body>
+</html>
diff --git a/demo/src/index.ts b/demo/src/index.ts
new file mode 100644
index 0000000..22e8143
--- /dev/null
+++ b/demo/src/index.ts
@@ -0,0 +1,105 @@
+import { buildHex } from './compile';
+import './index.css';
+import { AVRRunner } from './execute';
+import { formatTime } from './format-time';
+import { LED } from './led';
+
+let editor: any;
+const BLINK_CODE = `
+// Green LED connected to LED_BUILTIN,
+// Red LED connected to pin 12. Enjoy!
+
+void setup() {
+ pinMode(LED_BUILTIN, OUTPUT);
+}
+
+void loop() {
+ digitalWrite(LED_BUILTIN, HIGH);
+ delay(500);
+ digitalWrite(LED_BUILTIN, LOW);
+ delay(500);
+}`.trim();
+
+// Load Editor
+declare var window: any;
+declare var monaco: any;
+window.require.config({
+ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.18.0/min/vs' }
+});
+window.require(['vs/editor/editor.main'], () => {
+ editor = monaco.editor.create(document.querySelector('.code-editor'), {
+ value: BLINK_CODE,
+ language: 'cpp',
+ minimap: { enabled: false }
+ });
+});
+
+// Set up LEDs
+const leds = document.querySelector('.leds');
+const led13 = new LED({ color: 'green', lightColor: '#80ff80' });
+const led12 = new LED({ color: 'red', lightColor: '#ff8080' });
+leds.appendChild(led13.el);
+leds.appendChild(led12.el);
+
+// Set up toolbar
+let runner: AVRRunner;
+
+const runButton = document.querySelector('#run-button');
+runButton.addEventListener('click', compileAndRun);
+const stopButton = document.querySelector('#stop-button');
+stopButton.addEventListener('click', stopCode);
+const statusLabel = document.querySelector('#status-label');
+const compilerOutputText = document.querySelector('#compiler-output-text');
+
+function executeProgram(hex: string) {
+ runner = new AVRRunner(hex);
+ const MHZ = 16000000;
+
+ // Hook to PORTB output
+ runner.cpu.writeHooks[0x25] = (value: number) => {
+ const DDRB = runner.cpu.data[0x24];
+ value &= DDRB;
+ const D12bit = 1 << 4;
+ const D13bit = 1 << 5;
+ led12.value = value & D12bit ? true : false;
+ led13.value = value & D13bit ? true : false;
+ };
+
+ runner.execute((cpu) => {
+ const time = formatTime(cpu.cycles / MHZ);
+ statusLabel.textContent = 'Simulation time: ' + time;
+ });
+}
+
+async function compileAndRun() {
+ led12.value = false;
+ led13.value = false;
+
+ runButton.setAttribute('disabled', '1');
+ try {
+ statusLabel.textContent = 'Compiling...';
+ const result = await buildHex(editor.getModel().getValue());
+ compilerOutputText.textContent = result.stderr || result.stdout;
+ if (result.hex) {
+ compilerOutputText.textContent += '\nProgram running...';
+ stopButton.removeAttribute('disabled');
+ executeProgram(result.hex);
+ } else {
+ runButton.removeAttribute('disabled');
+ }
+ } catch (err) {
+ runButton.removeAttribute('disabled');
+ alert('Failed: ' + err);
+ } finally {
+ statusLabel.textContent = '';
+ }
+}
+
+function stopCode() {
+ stopButton.setAttribute('disabled', '1');
+ runButton.removeAttribute('disabled');
+ if (runner) {
+ runner.stop();
+ runner = null;
+ }
+}
diff --git a/demo/src/intelhex.ts b/demo/src/intelhex.ts
new file mode 100644
index 0000000..ba5d6c8
--- /dev/null
+++ b/demo/src/intelhex.ts
@@ -0,0 +1,18 @@
+/**
+ * Minimal Intel HEX loader
+ * Part of AVR8js
+ *
+ * Copyright (C) 2019, Uri Shaked
+ */
+
+export function loadHex(source: string, target: Uint8Array) {
+ for (const line of source.split('\n')) {
+ if (line[0] === ':' && line.substr(7, 2) === '00') {
+ const bytes = parseInt(line.substr(1, 2), 16);
+ const addr = parseInt(line.substr(3, 4), 16);
+ for (let i = 0; i < bytes; i++) {
+ target[addr + i] = parseInt(line.substr(9 + i * 2, 2), 16);
+ }
+ }
+ }
+}
diff --git a/demo/src/led.ts b/demo/src/led.ts
new file mode 100644
index 0000000..8c6deb4
--- /dev/null
+++ b/demo/src/led.ts
@@ -0,0 +1,102 @@
+const led = `<svg
+width="40"
+height="50"
+version="1.2"
+viewBox="-10 -5 35.456 39.618"
+xmlns="http://www.w3.org/2000/svg"
+>
+<filter id="light1" x="-0.8" y="-0.8" height="2.2" width="2.8">
+ <feGaussianBlur stdDeviation="2.6" />
+</filter>
+<filter id="light2" x="-0.8" y="-0.8" height="2.2" width="2.8">
+ <feGaussianBlur stdDeviation="4" />
+</filter>
+<rect x="3.451" y="19.379" width="2.1514" height="9.8273" fill="#8c8c8c" />
+<path
+ d="m12.608 29.618c0-1.1736-0.86844-2.5132-1.8916-3.4024-0.41616-0.3672-1.1995-1.0015-1.1995-1.4249v-5.4706h-2.1614v5.7802c0 1.0584 0.94752 1.8785 1.9462 2.7482 0.44424 0.37584 1.3486 1.2496 1.3486 1.7694"
+ fill="#8c8c8c"
+/>
+<path
+ d="m14.173 13.001v-5.9126c0-3.9132-3.168-7.0884-7.0855-7.0884-3.9125 0-7.0877 3.1694-7.0877 7.0884v13.649c1.4738 1.651 4.0968 2.7526 7.0877 2.7526 4.6195 0 8.3686-2.6179 8.3686-5.8594v-1.5235c-7.4e-4 -1.1426-0.47444-2.2039-1.283-3.1061z"
+ opacity=".3"
+/>
+<path
+ d="m14.173 13.001v-5.9126c0-3.9132-3.168-7.0884-7.0855-7.0884-3.9125 0-7.0877 3.1694-7.0877 7.0884v13.649c1.4738 1.651 4.0968 2.7526 7.0877 2.7526 4.6195 0 8.3686-2.6179 8.3686-5.8594v-1.5235c-7.4e-4 -1.1426-0.47444-2.2039-1.283-3.1061z"
+ fill="#e6e6e6"
+ opacity=".5"
+/>
+<path
+ d="m14.173 13.001v3.1054c0 2.7389-3.1658 4.9651-7.0855 4.9651-3.9125 2e-5 -7.0877-2.219-7.0877-4.9651v4.6296c1.4738 1.6517 4.0968 2.7526 7.0877 2.7526 4.6195 0 8.3686-2.6179 8.3686-5.8586l-4e-5 -1.5235c-7e-4 -1.1419-0.4744-2.2032-1.283-3.1054z"
+ fill="#d1d1d1"
+ opacity=".9"
+/>
+<g>
+ <path
+ d="m14.173 13.001v3.1054c0 2.7389-3.1658 4.9651-7.0855 4.9651-3.9125 2e-5 -7.0877-2.219-7.0877-4.9651v4.6296c1.4738 1.6517 4.0968 2.7526 7.0877 2.7526 4.6195 0 8.3686-2.6179 8.3686-5.8586l-4e-5 -1.5235c-7e-4 -1.1419-0.4744-2.2032-1.283-3.1054z"
+ opacity=".7"
+ />
+ <path
+ d="m14.173 13.001v3.1054c0 2.7389-3.1658 4.9651-7.0855 4.9651-3.9125 2e-5 -7.0877-2.219-7.0877-4.9651v3.1054c1.4738 1.6502 4.0968 2.7526 7.0877 2.7526 4.6195 0 8.3686-2.6179 8.3686-5.8586-7.4e-4 -1.1412-0.47444-2.2025-1.283-3.1047z"
+ opacity=".25"
+ />
+ <ellipse cx="7.0877" cy="16.106" rx="7.087" ry="4.9608" opacity=".25" />
+</g>
+<polygon
+ points="2.2032 16.107 3.1961 16.107 3.1961 13.095 6.0156 13.095 10.012 8.8049 3.407 8.8049 2.2032 9.648"
+ fill="#666666"
+/>
+<polygon
+ points="11.215 9.0338 7.4117 13.095 11.06 13.095 11.06 16.107 11.974 16.107 11.974 8.5241 10.778 8.5241"
+ fill="#666666"
+/>
+<path
+ d="m14.173 13.001v-5.9126c0-3.9132-3.168-7.0884-7.0855-7.0884-3.9125 0-7.0877 3.1694-7.0877 7.0884v13.649c1.4738 1.651 4.0968 2.7526 7.0877 2.7526 4.6195 0 8.3686-2.6179 8.3686-5.8594v-1.5235c-7.4e-4 -1.1426-0.47444-2.2039-1.283-3.1061z"
+ fill="{{color}}"
+ opacity=".65"
+/>
+<g fill="#ffffff">
+ <path
+ d="m10.388 3.7541 1.4364-0.2736c-0.84168-1.1318-2.0822-1.9577-3.5417-2.2385l0.25416 1.0807c0.76388 0.27072 1.4068 0.78048 1.8511 1.4314z"
+ opacity=".5"
+ />
+ <path
+ d="m0.76824 19.926v1.5199c0.64872 0.5292 1.4335 0.97632 2.3076 1.3169v-1.525c-0.8784-0.33624-1.6567-0.78194-2.3076-1.3118z"
+ opacity=".5"
+ />
+ <path
+ d="m11.073 20.21c-0.2556 0.1224-0.52992 0.22968-0.80568 0.32976-0.05832 0.01944-0.11736 0.04032-0.17784 0.05832-0.56376 0.17928-1.1614 0.31896-1.795 0.39456-0.07488 0.0094-0.1512 0.01872-0.22464 0.01944-0.3204 0.03024-0.64368 0.05832-0.97056 0.05832-0.14832 0-0.30744-0.01512-0.4716-0.02376-1.2002-0.05688-2.3306-0.31464-3.2976-0.73944l-2e-5 -8.3895v-4.8254c0-1.471 0.84816-2.7295 2.0736-3.3494l-0.02232-0.05328-1.2478-1.512c-1.6697 1.003-2.79 2.8224-2.79 4.9118v11.905c-0.04968-0.04968-0.30816-0.30888-0.48024-0.52992l-0.30744 0.6876c1.4011 1.4818 3.8088 2.4617 6.5426 2.4617 1.6798 0 3.2371-0.37368 4.5115-1.0022l-0.52704-0.40896-0.01006 0.0072z"
+ opacity=".5"
+ />
+</g>
+<g class="light">
+ <ellipse cx="8" cy="10" rx="10" ry="10" fill="{{lightColor}}" filter="url(#light2)"></ellipse>
+ <ellipse cx="8" cy="10" rx="3" ry="3" fill="white" filter="url(#light1)"></ellipse>
+</g>
+</svg>
+`;
+
+export interface ILEDOptions {
+ color: string;
+ lightColor?: string;
+}
+
+const ON_CLASS = 'led-on';
+export class LED {
+ readonly el = document.createElement('span');
+ private readonly lightEl: SVGElement;
+ constructor({ color, lightColor }: ILEDOptions) {
+ this.el.innerHTML = led
+ .replace('{{color}}', color)
+ .replace('{{lightColor}}', lightColor || color);
+ this.lightEl = this.el.querySelector('.light');
+ this.lightEl.style.display = 'none';
+ }
+
+ get value() {
+ return this.lightEl.style.display !== 'none';
+ }
+
+ set value(value: boolean) {
+ this.lightEl.style.display = value ? '' : 'none';
+ }
+}