Skip to content

Commit b9dfd55

Browse files
committed
feat: add blink demo
1 parent 1709b3d commit b9dfd55

File tree

14 files changed

+7885
-3553
lines changed

14 files changed

+7885
-3553
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.cache
12
node_modules
23
dist
34
*.log

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ JavaScript implementation of the AVR 8-bit architecture
44

55
[![Build Status](https://travis-ci.org/wokwi/avr8js.png?branch=master)](https://travis-ci.org/wokwi/avr8js)
66

7+
## Running the demo project
8+
9+
The demo project allows you to edit and run Arduino code. It includes 2 simulated LEDs
10+
connected to pins 12 and 13 (PB4 and PB5).
11+
12+
To run the demo project, check out this repository, run `npm install` and then `npm start`.
13+
714
## License
815

916
Copyright (C) 2019, Uri Shaked. The code is released under the terms of the MIT license.

demo/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist
2+
build

demo/src/compile.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const url = 'https://wokwi-hexi-73miufol2q-uc.a.run.app';
2+
3+
export interface IHexiResult {
4+
stdout: string;
5+
stderr: string;
6+
hex: string;
7+
}
8+
9+
export async function buildHex(source: string) {
10+
const resp = await fetch(url + '/build', {
11+
method: 'POST',
12+
mode: 'cors',
13+
cache: 'no-cache',
14+
headers: {
15+
'Content-Type': 'application/json'
16+
},
17+
body: JSON.stringify({ sketch: source })
18+
});
19+
return (await resp.json()) as IHexiResult;
20+
}

demo/src/execute.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { avrInstruction, AVRTimer, CPU, timer0Config } from 'avr8js';
2+
import { loadHex } from './intelhex';
3+
4+
// ATmega328p params
5+
const FLASH = 0x8000;
6+
7+
export class AVRRunner {
8+
readonly program = new Uint16Array(FLASH);
9+
readonly cpu: CPU;
10+
readonly timer: AVRTimer;
11+
12+
private stopped = false;
13+
14+
constructor(hex: string) {
15+
loadHex(hex, new Uint8Array(this.program.buffer));
16+
this.cpu = new CPU(this.program);
17+
this.timer = new AVRTimer(this.cpu, timer0Config);
18+
}
19+
20+
async execute(callback: (cpu: CPU) => void) {
21+
this.stopped = false;
22+
for (;;) {
23+
avrInstruction(this.cpu);
24+
this.timer.tick();
25+
if (this.cpu.cycles % 50000 === 0) {
26+
callback(this.cpu);
27+
await new Promise((resolve) => setTimeout(resolve, 0));
28+
if (this.stopped) {
29+
break;
30+
}
31+
}
32+
}
33+
}
34+
35+
stop() {
36+
this.stopped = true;
37+
}
38+
}

demo/src/format-time.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
function zeroPad(value: number, length: number) {
2+
let sval = value.toString();
3+
while (sval.length < length) {
4+
sval = '0' + sval;
5+
}
6+
return sval;
7+
}
8+
9+
export function formatTime(seconds: number) {
10+
const ms = Math.floor(seconds * 1000) % 1000;
11+
const secs = Math.floor(seconds % 60);
12+
const mins = Math.floor(seconds / 60);
13+
return `${zeroPad(mins, 2)}:${zeroPad(secs, 2)}.${zeroPad(ms, 3)}`;
14+
}

demo/src/index.css

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
body {
2+
padding: 0 16px;
3+
font-family: 'Roboto', sans-serif;
4+
width: 100%;
5+
box-sizing: border-box;
6+
}
7+
8+
.app-container {
9+
width: 500px;
10+
max-width: 100%;
11+
}
12+
13+
.toolbar {
14+
padding: 4px;
15+
display: flex;
16+
background-color: #ddd;
17+
box-sizing: border-box;
18+
width: 100%;
19+
}
20+
21+
.toolbar > button {
22+
margin-right: 4px;
23+
}
24+
25+
.spacer {
26+
flex: 1;
27+
}
28+
29+
.code-editor {
30+
width: 100%;
31+
max-width: 100%;
32+
height: 300px;
33+
box-sizing: border-box;
34+
border: 1px solid grey;
35+
}
36+
37+
.compiler-output {
38+
width: 500px;
39+
box-sizing: border-box;
40+
padding: 8px 12px;
41+
max-height: 120px;
42+
overflow: auto;
43+
}
44+
45+
.compiler-output pre {
46+
margin: 0;
47+
white-space: pre-line;
48+
}

demo/src/index.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7+
<title>AVR8js LED Demo</title>
8+
<link href="//fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet" />
9+
</head>
10+
<body>
11+
<h2>AVR8js LED Demo</h2>
12+
<div class="app-container">
13+
<div class="leds"></div>
14+
<div class="toolbar">
15+
<button id="run-button">Run</button>
16+
<button id="stop-button" disabled>Stop</button>
17+
<div class="spacer"></div>
18+
<div id="status-label"></div>
19+
</div>
20+
<div class="code-editor"></div>
21+
<div class="compiler-output">
22+
<pre id="compiler-output-text"></pre>
23+
</div>
24+
</div>
25+
<script src="//cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.18.0/min/vs/loader.js"></script>
26+
<script src="./index.ts"></script>
27+
</body>
28+
</html>

demo/src/index.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { buildHex } from './compile';
2+
import './index.css';
3+
import { AVRRunner } from './execute';
4+
import { formatTime } from './format-time';
5+
import { LED } from './led';
6+
7+
let editor: any;
8+
const BLINK_CODE = `
9+
// Green LED connected to LED_BUILTIN,
10+
// Red LED connected to pin 12. Enjoy!
11+
12+
void setup() {
13+
pinMode(LED_BUILTIN, OUTPUT);
14+
}
15+
16+
void loop() {
17+
digitalWrite(LED_BUILTIN, HIGH);
18+
delay(500);
19+
digitalWrite(LED_BUILTIN, LOW);
20+
delay(500);
21+
}`.trim();
22+
23+
// Load Editor
24+
declare var window: any;
25+
declare var monaco: any;
26+
window.require.config({
27+
paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.18.0/min/vs' }
28+
});
29+
window.require(['vs/editor/editor.main'], () => {
30+
editor = monaco.editor.create(document.querySelector('.code-editor'), {
31+
value: BLINK_CODE,
32+
language: 'cpp',
33+
minimap: { enabled: false }
34+
});
35+
});
36+
37+
// Set up LEDs
38+
const leds = document.querySelector('.leds');
39+
const led13 = new LED({ color: 'green', lightColor: '#80ff80' });
40+
const led12 = new LED({ color: 'red', lightColor: '#ff8080' });
41+
leds.appendChild(led13.el);
42+
leds.appendChild(led12.el);
43+
44+
// Set up toolbar
45+
let runner: AVRRunner;
46+
47+
const runButton = document.querySelector('#run-button');
48+
runButton.addEventListener('click', compileAndRun);
49+
const stopButton = document.querySelector('#stop-button');
50+
stopButton.addEventListener('click', stopCode);
51+
const statusLabel = document.querySelector('#status-label');
52+
const compilerOutputText = document.querySelector('#compiler-output-text');
53+
54+
function executeProgram(hex: string) {
55+
runner = new AVRRunner(hex);
56+
const MHZ = 16000000;
57+
58+
// Hook to PORTB output
59+
runner.cpu.writeHooks[0x25] = (value: number) => {
60+
const DDRB = runner.cpu.data[0x24];
61+
value &= DDRB;
62+
const D12bit = 1 << 4;
63+
const D13bit = 1 << 5;
64+
led12.value = value & D12bit ? true : false;
65+
led13.value = value & D13bit ? true : false;
66+
};
67+
68+
runner.execute((cpu) => {
69+
const time = formatTime(cpu.cycles / MHZ);
70+
statusLabel.textContent = 'Simulation time: ' + time;
71+
});
72+
}
73+
74+
async function compileAndRun() {
75+
led12.value = false;
76+
led13.value = false;
77+
78+
runButton.setAttribute('disabled', '1');
79+
try {
80+
statusLabel.textContent = 'Compiling...';
81+
const result = await buildHex(editor.getModel().getValue());
82+
compilerOutputText.textContent = result.stderr || result.stdout;
83+
if (result.hex) {
84+
compilerOutputText.textContent += '\nProgram running...';
85+
stopButton.removeAttribute('disabled');
86+
executeProgram(result.hex);
87+
} else {
88+
runButton.removeAttribute('disabled');
89+
}
90+
} catch (err) {
91+
runButton.removeAttribute('disabled');
92+
alert('Failed: ' + err);
93+
} finally {
94+
statusLabel.textContent = '';
95+
}
96+
}
97+
98+
function stopCode() {
99+
stopButton.setAttribute('disabled', '1');
100+
runButton.removeAttribute('disabled');
101+
if (runner) {
102+
runner.stop();
103+
runner = null;
104+
}
105+
}

demo/src/intelhex.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Minimal Intel HEX loader
3+
* Part of AVR8js
4+
*
5+
* Copyright (C) 2019, Uri Shaked
6+
*/
7+
8+
export function loadHex(source: string, target: Uint8Array) {
9+
for (const line of source.split('\n')) {
10+
if (line[0] === ':' && line.substr(7, 2) === '00') {
11+
const bytes = parseInt(line.substr(1, 2), 16);
12+
const addr = parseInt(line.substr(3, 4), 16);
13+
for (let i = 0; i < bytes; i++) {
14+
target[addr + i] = parseInt(line.substr(9 + i * 2, 2), 16);
15+
}
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)