aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/index.ts1
-rw-r--r--src/usart.spec.ts60
-rw-r--r--src/usart.ts119
3 files changed, 180 insertions, 0 deletions
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;
+ }
+ }
+}