aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals
diff options
context:
space:
mode:
Diffstat (limited to 'src/peripherals')
-rw-r--r--src/peripherals/usart.spec.ts136
-rw-r--r--src/peripherals/usart.ts68
2 files changed, 150 insertions, 54 deletions
diff --git a/src/peripherals/usart.spec.ts b/src/peripherals/usart.spec.ts
index 04e377b..2728f9c 100644
--- a/src/peripherals/usart.spec.ts
+++ b/src/peripherals/usart.spec.ts
@@ -22,6 +22,9 @@ const UDRIE = 0x20;
const TXCIE = 0x40;
const TXC = 0x40;
const UDRE = 0x20;
+const USBS = 0x08;
+const UPM0 = 0x10;
+const UPM1 = 0x20;
// Interrupt address
const PC_INT_UDRE = 0x26;
@@ -48,40 +51,87 @@ describe('USART', () => {
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(UCSR0C, 0);
- expect(usart.bitsPerChar).toEqual(5);
- });
+ describe('bitsPerChar', () => {
+ 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(UCSR0C, 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(UCSR0C, UCSZ0);
- expect(usart.bitsPerChar).toEqual(6);
+ 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(UCSR0C, UCSZ0);
+ 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(UCSR0C, UCSZ1);
+ 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(UCSR0C, UCSZ0 | UCSZ1);
+ 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(UCSR0C, UCSZ0 | UCSZ1);
+ cpu.writeData(UCSR0B, UCSZ2);
+ expect(usart.bitsPerChar).toEqual(9);
+ });
});
- 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(UCSR0C, UCSZ1);
- expect(usart.bitsPerChar).toEqual(7);
+ describe('stopBits', () => {
+ it('should return 1 when USBS = 0', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ expect(usart.stopBits).toEqual(1);
+ });
+
+ it('should return 2 when USBS = 1', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(UCSR0C, USBS);
+ expect(usart.stopBits).toEqual(2);
+ });
});
- 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(UCSR0C, UCSZ0 | UCSZ1);
- expect(usart.bitsPerChar).toEqual(8);
+ describe('parityEnabled', () => {
+ it('should return false when UPM1 = 0', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ expect(usart.parityEnabled).toEqual(false);
+ });
+
+ it('should return true when UPM1 = 1', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(UCSR0C, UPM1);
+ expect(usart.parityEnabled).toEqual(true);
+ });
});
- 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(UCSR0C, UCSZ0 | UCSZ1);
- cpu.writeData(UCSR0B, UCSZ2);
- expect(usart.bitsPerChar).toEqual(9);
+ describe('parityOdd', () => {
+ it('should return false when UPM0 = 0', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ expect(usart.parityOdd).toEqual(false);
+ });
+
+ it('should return true when UPM0 = 1', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(UCSR0C, UPM0);
+ expect(usart.parityOdd).toEqual(true);
+ });
});
it('should invoke onByteTransmit when UDR0 is written to', () => {
@@ -93,21 +143,11 @@ describe('USART', () => {
expect(usart.onByteTransmit).toHaveBeenCalledWith(0x61);
});
- it('should set UDRE and TXC flags after UDR0', () => {
- const cpu = new CPU(new Uint16Array(1024));
- new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
- cpu.writeData(UCSR0B, TXEN);
- cpu.writeData(UCSR0A, 0);
- cpu.writeData(UDR0, 0x61);
- expect(cpu.data[UCSR0A]).toEqual(TXC | UDRE);
- });
-
describe('tick()', () => {
it('should trigger data register empty interrupt if UDRE is set', () => {
const cpu = new CPU(new Uint16Array(1024));
const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
cpu.writeData(UCSR0B, UDRIE | TXEN);
- cpu.writeData(0xc6, 0x61);
cpu.data[SREG] = 0x80; // SREG: I-------
usart.tick();
expect(cpu.pc).toEqual(PC_INT_UDRE);
@@ -121,9 +161,10 @@ describe('USART', () => {
cpu.writeData(UCSR0B, TXCIE | TXEN);
cpu.writeData(UDR0, 0x61);
cpu.data[SREG] = 0x80; // SREG: I-------
+ cpu.cycles = 1e6;
usart.tick();
expect(cpu.pc).toEqual(PC_INT_TXC);
- expect(cpu.cycles).toEqual(2);
+ expect(cpu.cycles).toEqual(1e6 + 2);
expect(cpu.data[UCSR0A] & TXC).toEqual(0);
});
@@ -143,9 +184,10 @@ describe('USART', () => {
cpu.writeData(UCSR0B, UDRIE | TXEN);
cpu.writeData(UDR0, 0x61);
cpu.data[SREG] = 0; // SREG: 0 (disable interrupts)
+ cpu.cycles = 1e6;
usart.tick();
expect(cpu.pc).toEqual(0);
- expect(cpu.cycles).toEqual(0);
+ expect(cpu.cycles).toEqual(1e6);
expect(cpu.data[UCSR0A]).toEqual(TXC | UDRE);
});
});
@@ -192,4 +234,20 @@ describe('USART', () => {
expect(usart.onLineTransmit).toHaveBeenCalledWith('there');
});
});
+
+ describe('integration', () => {
+ it('should set the TXC bit after ~1.04mS when baud rate set to 9600', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
+ cpu.writeData(UCSR0B, TXEN);
+ cpu.writeData(UBRR0L, 103); // baud: 9600
+ cpu.writeData(UDR0, 0x48); // 'H'
+ cpu.cycles += 16000; // 1ms
+ usart.tick();
+ expect(cpu.data[UCSR0A] & TXC).toEqual(0);
+ cpu.cycles += 800; // 0.05ms
+ usart.tick();
+ expect(cpu.data[UCSR0A] & TXC).toEqual(TXC);
+ });
+ });
});
diff --git a/src/peripherals/usart.ts b/src/peripherals/usart.ts
index 16d86f8..a2037a9 100644
--- a/src/peripherals/usart.ts
+++ b/src/peripherals/usart.ts
@@ -1,3 +1,11 @@
+/**
+ * AVR-8 USART Peripheral
+ * Part of AVR8js
+ * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf
+ *
+ * Copyright (C) 2019, 2020, Uri Shaked
+ */
+
import { CPU } from '../cpu/cpu';
import { avrInterrupt } from '../cpu/interrupt';
import { u8 } from '../types';
@@ -64,11 +72,10 @@ export class AVRUSART {
private lineBuffer = '';
+ private txCompleteCycles = 0;
+
constructor(private cpu: CPU, private config: USARTConfig, private freqMHz: number) {
- this.cpu.writeHooks[config.UCSRA] = (value) => {
- this.cpu.data[config.UCSRA] = value | UCSRA_UDRE | UCSRA_TXC;
- return true;
- };
+ this.reset();
this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => {
if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) {
// Enabling the transmission - mark UDR as empty
@@ -88,29 +95,48 @@ export class AVRUSART {
this.lineBuffer += ch;
}
}
- this.cpu.data[config.UCSRA] |= UCSRA_UDRE | UCSRA_TXC;
+ const symbolsPerChar = 1 + this.bitsPerChar + this.stopBits + (this.parityEnabled ? 1 : 0);
+ this.txCompleteCycles = this.cpu.cycles + (this.UBRR * this.multiplier + 1) * symbolsPerChar;
+ this.cpu.data[config.UCSRA] &= ~(UCSRA_TXC | UCSRA_UDRE);
};
}
+ reset() {
+ this.cpu.data[this.config.UCSRA] = UCSRA_UDRE;
+ this.cpu.data[this.config.UCSRB] = 0;
+ this.cpu.data[this.config.UCSRC] = UCSRC_UCSZ1 | UCSRC_UCSZ0; // default: 8 bits per byte
+ }
+
tick() {
- if (this.cpu.interruptsEnabled) {
- const ucsra = this.cpu.data[this.config.UCSRA];
- const ucsrb = this.cpu.data[this.config.UCSRB];
+ const { txCompleteCycles, cpu } = this;
+ if (txCompleteCycles && cpu.cycles >= txCompleteCycles) {
+ this.cpu.data[this.config.UCSRA] |= UCSRA_UDRE | UCSRA_TXC;
+ this.txCompleteCycles = 0;
+ }
+ if (cpu.interruptsEnabled) {
+ const ucsra = cpu.data[this.config.UCSRA];
+ const ucsrb = 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;
+ avrInterrupt(cpu, this.config.dataRegisterEmptyInterrupt);
+ cpu.data[this.config.UCSRA] &= ~UCSRA_UDRE;
}
if (ucsra & UCSRA_TXC && ucsrb & UCSRB_TXCIE) {
- avrInterrupt(this.cpu, this.config.txCompleteInterrupt);
- this.cpu.data[this.config.UCSRA] &= ~UCSRA_TXC;
+ avrInterrupt(cpu, this.config.txCompleteInterrupt);
+ cpu.data[this.config.UCSRA] &= ~UCSRA_TXC;
}
}
}
+ private get UBRR() {
+ return (this.cpu.data[this.config.UBRRH] << 8) | this.cpu.data[this.config.UBRRL];
+ }
+
+ private get multiplier() {
+ return this.cpu.data[this.config.UCSRA] & UCSRA_U2X ? 8 : 16;
+ }
+
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)));
+ return Math.floor(this.freqMHz / (this.multiplier * (1 + this.UBRR)));
}
get bitsPerChar() {
@@ -131,4 +157,16 @@ export class AVRUSART {
return 9;
}
}
+
+ get stopBits() {
+ return this.cpu.data[this.config.UCSRC] & UCSRC_USBS ? 2 : 1;
+ }
+
+ get parityEnabled() {
+ return this.cpu.data[this.config.UCSRC] & UCSRC_UPM1 ? true : false;
+ }
+
+ get parityOdd() {
+ return this.cpu.data[this.config.UCSRC] & UCSRC_UPM0 ? true : false;
+ }
}