aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUri Shaked2019-12-01 15:18:25 +0200
committerUri Shaked2019-12-01 15:18:25 +0200
commit2fd9e6d4c040d4a54456b153f4523cab05adbf02 (patch)
treeaf194f254e818c1922efe6e135a071879821b32a
parentchore: release 0.3.3 (diff)
downloadavr8js-2fd9e6d4c040d4a54456b153f4523cab05adbf02.tar.gz
avr8js-2fd9e6d4c040d4a54456b153f4523cab05adbf02.tar.bz2
avr8js-2fd9e6d4c040d4a54456b153f4523cab05adbf02.zip
feat: initial implementation of USART
#6
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;
+ }
+ }
+}
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.