From b9dfd552a62a46449532d49adc0773589076c808 Mon Sep 17 00:00:00 2001
From: Uri Shaked
Date: Thu, 21 Nov 2019 19:40:02 +0200
Subject: feat: add blink demo
---
demo/.gitignore | 2 +
demo/src/compile.ts | 20 +++++++++
demo/src/execute.ts | 38 ++++++++++++++++++
demo/src/format-time.ts | 14 +++++++
demo/src/index.css | 48 ++++++++++++++++++++++
demo/src/index.html | 28 +++++++++++++
demo/src/index.ts | 105 ++++++++++++++++++++++++++++++++++++++++++++++++
demo/src/intelhex.ts | 18 +++++++++
demo/src/led.ts | 102 ++++++++++++++++++++++++++++++++++++++++++++++
demo/tsconfig.json | 12 ++++++
10 files changed, 387 insertions(+)
create mode 100644 demo/.gitignore
create mode 100644 demo/src/compile.ts
create mode 100644 demo/src/execute.ts
create mode 100644 demo/src/format-time.ts
create mode 100644 demo/src/index.css
create mode 100644 demo/src/index.html
create mode 100644 demo/src/index.ts
create mode 100644 demo/src/intelhex.ts
create mode 100644 demo/src/led.ts
create mode 100644 demo/tsconfig.json
(limited to 'demo')
diff --git a/demo/.gitignore b/demo/.gitignore
new file mode 100644
index 0000000..1b823a2
--- /dev/null
+++ b/demo/.gitignore
@@ -0,0 +1,2 @@
+dist
+build
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 @@
+
+
+
+
+
+
+ AVR8js LED Demo
+
+
+
+ AVR8js LED Demo
+
+
+
+
+
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 = `
+`;
+
+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';
+ }
+}
diff --git a/demo/tsconfig.json b/demo/tsconfig.json
new file mode 100644
index 0000000..2c2940e
--- /dev/null
+++ b/demo/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "baseUrl": "..",
+ "rootDir": "..",
+ "lib": ["dom"],
+ "paths": {
+ "avr8js": ["src"]
+ }
+ },
+ "include": ["fuse.ts", "src/**/*.ts"]
+}
--
cgit v1.2.3