diff options
| author | Uri Shaked | 2019-11-21 19:40:02 +0200 |
|---|---|---|
| committer | Uri Shaked | 2019-11-21 20:05:23 +0200 |
| commit | b9dfd552a62a46449532d49adc0773589076c808 (patch) | |
| tree | 8eb1ec1f49e7b8e097a51ee6bf266a609eafac43 /demo | |
| parent | chore: release 0.2.0 (diff) | |
| download | avr8js-b9dfd552a62a46449532d49adc0773589076c808.tar.gz avr8js-b9dfd552a62a46449532d49adc0773589076c808.tar.bz2 avr8js-b9dfd552a62a46449532d49adc0773589076c808.zip | |
feat: add blink demo
Diffstat (limited to '')
| -rw-r--r-- | demo/.gitignore | 2 | ||||
| -rw-r--r-- | demo/src/compile.ts | 20 | ||||
| -rw-r--r-- | demo/src/execute.ts | 38 | ||||
| -rw-r--r-- | demo/src/format-time.ts | 14 | ||||
| -rw-r--r-- | demo/src/index.css | 48 | ||||
| -rw-r--r-- | demo/src/index.html | 28 | ||||
| -rw-r--r-- | demo/src/index.ts | 105 | ||||
| -rw-r--r-- | demo/src/intelhex.ts | 18 | ||||
| -rw-r--r-- | demo/src/led.ts | 102 | ||||
| -rw-r--r-- | demo/tsconfig.json | 12 |
10 files changed, 387 insertions, 0 deletions
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 @@ +<!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'; + } +} 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"] +} |
