From 3d095d34a8b3c51e621f883e3c11039bd87a2910 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Fri, 27 Nov 2020 01:51:35 +0200 Subject: fix(usart): respect the given baud rate #16 close #16 --- src/peripherals/usart.spec.ts | 136 ++++++++++++++++++++++++++++++------------ src/peripherals/usart.ts | 68 ++++++++++++++++----- 2 files changed, 150 insertions(+), 54 deletions(-) (limited to 'src/peripherals') 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; + } } -- cgit v1.2.3