aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--demo/src/execute.ts8
-rw-r--r--demo/src/index.css4
-rw-r--r--demo/src/index.html1
-rw-r--r--demo/src/index.ts7
-rw-r--r--src/index.ts1
-rw-r--r--src/usart.spec.ts60
-rw-r--r--src/usart.ts119
7 files changed, 199 insertions, 1 deletions
diff --git a/demo/src/execute.ts b/demo/src/execute.ts
index 4568839..f2c7d82 100644
--- a/demo/src/execute.ts
+++ b/demo/src/execute.ts
@@ -4,9 +4,11 @@ import {
CPU,
timer0Config,
AVRIOPort,
+ AVRUSART,
portBConfig,
portCConfig,
- portDConfig
+ portDConfig,
+ usart0Config
} from 'avr8js';
import { loadHex } from './intelhex';
@@ -20,6 +22,8 @@ export class AVRRunner {
readonly portB: AVRIOPort;
readonly portC: AVRIOPort;
readonly portD: AVRIOPort;
+ readonly usart: AVRUSART;
+ readonly speed = 16e6; // 16 MHZ
private stopped = false;
@@ -30,6 +34,7 @@ export class AVRRunner {
this.portB = new AVRIOPort(this.cpu, portBConfig);
this.portC = new AVRIOPort(this.cpu, portCConfig);
this.portD = new AVRIOPort(this.cpu, portDConfig);
+ this.usart = new AVRUSART(this.cpu, usart0Config, this.speed);
}
async execute(callback: (cpu: CPU) => void) {
@@ -37,6 +42,7 @@ export class AVRRunner {
for (;;) {
avrInstruction(this.cpu);
this.timer.tick();
+ this.usart.tick();
if (this.cpu.cycles % 50000 === 0) {
callback(this.cpu);
await new Promise((resolve) => setTimeout(resolve, 0));
diff --git a/demo/src/index.css b/demo/src/index.css
index a3fa8b8..6801204 100644
--- a/demo/src/index.css
+++ b/demo/src/index.css
@@ -46,3 +46,7 @@ body {
margin: 0;
white-space: pre-line;
}
+
+#serial-output-text {
+ color: blue;
+}
diff --git a/demo/src/index.html b/demo/src/index.html
index 935bcff..2a8e924 100644
--- a/demo/src/index.html
+++ b/demo/src/index.html
@@ -20,6 +20,7 @@
<div class="code-editor"></div>
<div class="compiler-output">
<pre id="compiler-output-text"></pre>
+ <pre id="serial-output-text"></pre>
</div>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.18.0/min/vs/loader.js"></script>
diff --git a/demo/src/index.ts b/demo/src/index.ts
index e51cbbc..7b6f9b1 100644
--- a/demo/src/index.ts
+++ b/demo/src/index.ts
@@ -10,10 +10,12 @@ const BLINK_CODE = `
// Red LED connected to pin 12. Enjoy!
void setup() {
+ Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
+ Serial.println("Blink");
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
@@ -50,6 +52,7 @@ const stopButton = document.querySelector('#stop-button');
stopButton.addEventListener('click', stopCode);
const statusLabel = document.querySelector('#status-label');
const compilerOutputText = document.querySelector('#compiler-output-text');
+const serialOutputText = document.querySelector('#serial-output-text');
function executeProgram(hex: string) {
runner = new AVRRunner(hex);
@@ -62,6 +65,9 @@ function executeProgram(hex: string) {
led12.value = value & D12bit ? true : false;
led13.value = value & D13bit ? true : false;
});
+ runner.usart.onByteTransmit = (value) => {
+ serialOutputText.textContent += String.fromCharCode(value);
+ };
runner.execute((cpu) => {
const time = formatTime(cpu.cycles / MHZ);
statusLabel.textContent = 'Simulation time: ' + time;
@@ -73,6 +79,7 @@ async function compileAndRun() {
led13.value = false;
runButton.setAttribute('disabled', '1');
+ serialOutputText.textContent = '';
try {
statusLabel.textContent = 'Compiling...';
const result = await buildHex(editor.getModel().getValue());
diff --git a/src/index.ts b/src/index.ts
index 094d3a9..d8f4373 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -18,3 +18,4 @@ export {
portKConfig,
portLConfig
} from './gpio';
+export { AVRUSART, usart0Config } from './usart';
diff --git a/src/usart.spec.ts b/src/usart.spec.ts
new file mode 100644
index 0000000..230709f
--- /dev/null
+++ b/src/usart.spec.ts
@@ -0,0 +1,60 @@
+import { CPU } from './cpu';
+import { AVRUSART, usart0Config } from './usart';
+
+const FREQ_16MHZ = 16e6;
+const FREQ_11_0529MHZ = 11059200;
+
+describe('USART', () => {
+ it('should correctly calculate the baudRate from UBRR', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_11_0529MHZ);
+ cpu.writeData(0xc5, 0); // UBRR0H <- 0
+ cpu.writeData(0xc4, 5); // UBRR0L <- 5
+ expect(usart.baudRate).toEqual(115200);
+ });
+
+ it('should correctly calculate the baudRate from UBRR in double-speed mode', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(0xc5, 3); // UBRR0H <- 3
+ cpu.writeData(0xc4, 64); // UBRR0L <- 64
+ cpu.writeData(0xc0, 2); // UCSR0A: U2X0
+ expect(usart.baudRate).toEqual(2400);
+ });
+
+ it('should return 5-bits per byte when UCSZ = 0', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(0xc0, 0);
+ expect(usart.bitsPerChar).toEqual(5);
+ });
+
+ it('should return 6-bits per byte when UCSZ = 1', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(0xc0, 0x2);
+ expect(usart.bitsPerChar).toEqual(6);
+ });
+
+ it('should return 7-bits per byte when UCSZ = 2', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(0xc0, 0x4);
+ expect(usart.bitsPerChar).toEqual(7);
+ });
+
+ it('should return 8-bits per byte when UCSZ = 3', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(0xc0, 0x6);
+ expect(usart.bitsPerChar).toEqual(8);
+ });
+
+ it('should return 9-bits per byte when UCSZ = 7', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(0xc0, 0x6);
+ cpu.writeData(0xc1, 0x4);
+ expect(usart.bitsPerChar).toEqual(9);
+ });
+});
diff --git a/src/usart.ts b/src/usart.ts
new file mode 100644
index 0000000..276207e
--- /dev/null
+++ b/src/usart.ts
@@ -0,0 +1,119 @@
+import { CPU } from './cpu';
+import { avrInterrupt } from './interrupt';
+import { u8 } from './types';
+
+export interface USARTConfig {
+ rxCompleteInterrupt: u8;
+ dataRegisterEmptyInterrupt: u8;
+ txCompleteInterrupt: u8;
+
+ UCSRA: u8;
+ UCSRB: u8;
+ UCSRC: u8;
+ UBRRL: u8;
+ UBRRH: u8;
+ UDR: u8;
+}
+
+export const usart0Config: USARTConfig = {
+ rxCompleteInterrupt: 0x24,
+ dataRegisterEmptyInterrupt: 0x26,
+ txCompleteInterrupt: 0x28,
+ UCSRA: 0xc0,
+ UCSRB: 0xc1,
+ UCSRC: 0xc2,
+ UBRRL: 0xc4,
+ UBRRH: 0xc5,
+ UDR: 0xc6
+};
+
+export type USARTTransmitCallback = (value: u8) => void;
+
+// Register bits
+const UCSRA_RXC = 0x80; // USART Receive Complete
+const UCSRA_TXC = 0x40; // USART Transmit Complete
+const UCSRA_UDRE = 0x20; // USART Data Register Empty
+const UCSRA_FE = 0x10; // Frame Error
+const UCSRA_DOR = 0x8; // Data OverRun
+const UCSRA_UPE = 0x4; // USART Parity Error
+const UCSRA_U2X = 0x2; // Double the USART Transmission Speed
+const UCSRA_MPCM = 0x1; // Multi-processor Communication Mode
+const UCSRB_RXCIE = 0x80; // RX Complete Interrupt Enable
+const UCSRB_TXCIE = 0x40; // TX Complete Interrupt Enable
+const UCSRB_UDRIE = 0x20; // USART Data Register Empty Interrupt Enable
+const UCSRB_RXEN = 0x10; // Receiver Enable
+const UCSRB_TXEN = 0x8; // Transmitter Enable
+const UCSRB_UCSZ2 = 0x4; // Character Size 2
+const UCSRB_RXB8 = 0x2; // Receive Data Bit 8
+const UCSRB_TXB8 = 0x1; // Transmit Data Bit 8
+const UCSRC_UMSEL1 = 0x80; // USART Mode Select 1
+const UCSRC_UMSEL0 = 0x40; // USART Mode Select 0
+const UCSRC_UPM1 = 0x20; // Parity Mode 1
+const UCSRC_UPM0 = 0x10; // Parity Mode 0
+const UCSRC_USBS = 0x8; // Stop Bit Select
+const UCSRC_UCSZ1 = 0x4; // Character Size 1
+const UCSRC_UCSZ0 = 0x2; // Character Size 0
+const UCSRC_UCPOL = 0x1; // Clock Polarity
+
+export class AVRUSART {
+ public onByteTransmit: USARTTransmitCallback | null = null;
+
+ constructor(private cpu: CPU, private config: USARTConfig, private freqMHz: number) {
+ this.cpu.writeHooks[config.UCSRA] = (value, oldValue) => {
+ this.cpu.data[config.UCSRA] = value | UCSRA_UDRE | UCSRA_TXC;
+ return true;
+ };
+ this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => {
+ if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) {
+ // Enabling the transmission - mark UDR as empty
+ this.cpu.data[config.UCSRA] |= UCSRA_UDRE;
+ }
+ };
+ this.cpu.writeHooks[config.UDR] = (value) => {
+ if (this.onByteTransmit) {
+ this.onByteTransmit(value);
+ }
+ this.cpu.data[config.UCSRA] |= UCSRA_UDRE | UCSRA_TXC;
+ };
+ }
+
+ tick() {
+ if (this.cpu.interruptsEnabled) {
+ const ucsra = this.cpu.data[this.config.UCSRA];
+ const ucsrb = this.cpu.data[this.config.UCSRB];
+ if (ucsra & UCSRA_UDRE && ucsrb & UCSRB_UDRIE) {
+ avrInterrupt(this.cpu, this.config.dataRegisterEmptyInterrupt);
+ this.cpu.data[this.config.UCSRA] &= ~UCSRA_UDRE;
+ }
+ if (ucsrb & UCSRA_TXC && ucsrb & UCSRB_TXCIE) {
+ avrInterrupt(this.cpu, this.config.txCompleteInterrupt);
+ this.cpu.data[this.config.UCSRA] &= ~UCSRA_TXC;
+ }
+ }
+ }
+
+ get baudRate() {
+ const UBRR = (this.cpu.data[this.config.UBRRH] << 8) | this.cpu.data[this.config.UBRRL];
+ const multiplier = this.cpu.data[this.config.UCSRA] & UCSRA_U2X ? 8 : 16;
+ return Math.floor(this.freqMHz / (multiplier * (1 + UBRR)));
+ }
+
+ get bitsPerChar() {
+ const ucsz =
+ ((this.cpu.data[this.config.UCSRA] & (UCSRC_UCSZ1 | UCSRC_UCSZ0)) >> 1) |
+ (this.cpu.data[this.config.UCSRB] & UCSRB_UCSZ2);
+ switch (ucsz) {
+ case 0:
+ return 5;
+ case 1:
+ return 6;
+ case 2:
+ return 7;
+ case 3:
+ return 8;
+ default: // 4..6 are reserved
+ case 7:
+ return 9;
+ }
+ }
+}