From 2fd9e6d4c040d4a54456b153f4523cab05adbf02 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Sun, 1 Dec 2019 15:18:25 +0200 Subject: feat: initial implementation of USART #6 --- src/index.ts | 1 + src/usart.spec.ts | 60 +++++++++++++++++++++++++++ src/usart.ts | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 src/usart.spec.ts create mode 100644 src/usart.ts (limited to 'src') 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; + } + } +} -- cgit v1.2.3