From cf13e06ace0a132cc370e80a7dc87ea3f311bfb2 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Fri, 19 Feb 2021 23:43:34 +0200 Subject: feat(usart): implement RX #11 close #11 --- src/cpu/cpu.ts | 3 ++- src/peripherals/eeprom.ts | 1 + src/peripherals/usart.spec.ts | 39 ++++++++++++++++++++++++++- src/peripherals/usart.ts | 63 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 99 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/cpu/cpu.ts b/src/cpu/cpu.ts index a609222..93fa9cc 100644 --- a/src/cpu/cpu.ts +++ b/src/cpu/cpu.ts @@ -53,6 +53,7 @@ export interface AVRInterruptConfig { flagRegister: u16; flagMask: u8; constant?: boolean; + inverseFlag?: boolean; } export type AVRClockEventCallback = () => void; @@ -131,7 +132,7 @@ export class CPU implements ICPU { setInterruptFlag(interrupt: AVRInterruptConfig) { const { flagRegister, flagMask, enableRegister, enableMask } = interrupt; - if (interrupt.constant) { + if (interrupt.inverseFlag) { this.data[flagRegister] &= ~flagMask; } else { this.data[flagRegister] |= flagMask; diff --git a/src/peripherals/eeprom.ts b/src/peripherals/eeprom.ts index d492aa7..8056847 100644 --- a/src/peripherals/eeprom.ts +++ b/src/peripherals/eeprom.ts @@ -78,6 +78,7 @@ export class AVREEPROM { enableRegister: this.config.EECR, enableMask: EERIE, constant: true, + inverseFlag: true, }; constructor( diff --git a/src/peripherals/usart.spec.ts b/src/peripherals/usart.spec.ts index fb56967..f92ea00 100644 --- a/src/peripherals/usart.spec.ts +++ b/src/peripherals/usart.spec.ts @@ -18,8 +18,10 @@ const UDR0 = 0xc6; // Register bit names const U2X0 = 2; const TXEN = 8; +const RXEN = 16; const UDRIE = 0x20; const TXCIE = 0x40; +const RXC = 0x80; const TXC = 0x40; const UDRE = 0x20; const USBS = 0x08; @@ -235,7 +237,20 @@ describe('USART', () => { }); }); - describe('integration', () => { + describe('writeByte', () => { + it('should return false if called when RX is busy', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(UCSR0B, RXEN); + cpu.writeData(UBRR0L, 103); // baud: 9600 + expect(usart.writeByte(10)).toEqual(true); + expect(usart.writeByte(10)).toEqual(false); + cpu.tick(); + expect(usart.writeByte(10)).toEqual(false); + }); + }); + + describe('Integration tests', () => { it('should set the TXC bit after ~1.04mS when baud rate set to 9600', () => { const cpu = new CPU(new Uint16Array(1024)); new AVRUSART(cpu, usart0Config, FREQ_16MHZ); @@ -249,5 +264,27 @@ describe('USART', () => { cpu.tick(); expect(cpu.data[UCSR0A] & TXC).toEqual(TXC); }); + + it('should be ready to recieve the next byte after ~1.04ms when baudrate set to 9600', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + const rxCompleteCallback = jest.fn(); + usart.onRxComplete = rxCompleteCallback; + cpu.writeData(UCSR0B, RXEN); + cpu.writeData(UBRR0L, 103); // baud: 9600 + expect(usart.writeByte(0x42)).toBe(true); + cpu.cycles += 16000; // 1ms + cpu.tick(); + expect(cpu.data[UCSR0A] & RXC).toEqual(0); // byte not received yet + expect(usart.rxBusy).toBe(true); + expect(rxCompleteCallback).not.toHaveBeenCalled(); + cpu.cycles += 800; // 0.05ms + cpu.tick(); + expect(cpu.data[UCSR0A] & RXC).toEqual(RXC); + expect(usart.rxBusy).toBe(false); + expect(rxCompleteCallback).toHaveBeenCalled(); + expect(cpu.readData(UDR0)).toEqual(0x42); + expect(cpu.readData(UDR0)).toEqual(0); + }); }); }); diff --git a/src/peripherals/usart.ts b/src/peripherals/usart.ts index f9b0f87..f55fef3 100644 --- a/src/peripherals/usart.ts +++ b/src/peripherals/usart.ts @@ -65,13 +65,31 @@ const UCSRC_UCSZ0 = 0x2; // Character Size 0 const UCSRC_UCPOL = 0x1; // Clock Polarity /* eslint-enable @typescript-eslint/no-unused-vars */ +const rxMasks = { + 5: 0x1f, + 6: 0x3f, + 7: 0x7f, + 8: 0xff, + 9: 0xff, +}; export class AVRUSART { public onByteTransmit: USARTTransmitCallback | null = null; public onLineTransmit: USARTLineTransmitCallback | null = null; + public onRxComplete: (() => void) | null = null; + private rxBusyValue = false; + private rxByte = 0; private lineBuffer = ''; // Interrupts + private RXC: AVRInterruptConfig = { + address: this.config.rxCompleteInterrupt, + flagRegister: this.config.UCSRA, + flagMask: UCSRA_RXC, + enableRegister: this.config.UCSRB, + enableMask: UCSRB_RXCIE, + constant: true, + }; private UDRE: AVRInterruptConfig = { address: this.config.dataRegisterEmptyInterrupt, flagRegister: this.config.UCSRA, @@ -90,19 +108,29 @@ export class AVRUSART { constructor(private cpu: CPU, private config: USARTConfig, private freqHz: number) { this.reset(); this.cpu.writeHooks[config.UCSRA] = (value) => { - cpu.data[config.UCSRA] = value; - cpu.clearInterruptByFlag(this.UDRE, value); + cpu.data[config.UCSRA] = value & (UCSRA_MPCM | UCSRA_U2X); cpu.clearInterruptByFlag(this.TXC, value); return true; }; this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => { + cpu.updateInterruptEnable(this.RXC, value); cpu.updateInterruptEnable(this.UDRE, value); cpu.updateInterruptEnable(this.TXC, value); + if (value & UCSRB_RXEN && oldValue & UCSRB_RXEN) { + cpu.clearInterrupt(this.RXC); + } if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) { // Enabling the transmission - mark UDR as empty cpu.setInterruptFlag(this.UDRE); } }; + this.cpu.readHooks[config.UDR] = () => { + const mask = rxMasks[this.bitsPerChar] ?? 0xff; + const result = this.rxByte & mask; + this.rxByte = 0; + this.cpu.clearInterrupt(this.RXC); + return result; + }; this.cpu.writeHooks[config.UDR] = (value) => { if (this.onByteTransmit) { this.onByteTransmit(value); @@ -116,12 +144,10 @@ export class AVRUSART { this.lineBuffer += ch; } } - const symbolsPerChar = 1 + this.bitsPerChar + this.stopBits + (this.parityEnabled ? 1 : 0); - const cyclesToComplete = (this.UBRR * this.multiplier + 1) * symbolsPerChar; this.cpu.addClockEvent(() => { cpu.setInterruptFlag(this.UDRE); cpu.setInterruptFlag(this.TXC); - }, cyclesToComplete); + }, this.cyclesPerChar); this.cpu.clearInterrupt(this.TXC); this.cpu.clearInterrupt(this.UDRE); }; @@ -131,6 +157,33 @@ export class AVRUSART { 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 + this.rxBusyValue = false; + this.rxByte = 0; + this.lineBuffer = ''; + } + + get rxBusy() { + return this.rxBusyValue; + } + + writeByte(value: number) { + const { cpu, config } = this; + if (this.rxBusyValue || !(cpu.data[config.UCSRB] & UCSRB_RXEN)) { + return false; + } + this.rxBusyValue = true; + cpu.addClockEvent(() => { + this.rxByte = value; + this.rxBusyValue = false; + cpu.setInterruptFlag(this.RXC); + this.onRxComplete?.(); + }, this.cyclesPerChar); + return true; + } + + private get cyclesPerChar() { + const symbolsPerChar = 1 + this.bitsPerChar + this.stopBits + (this.parityEnabled ? 1 : 0); + return (this.UBRR * this.multiplier + 1) * symbolsPerChar; } private get UBRR() { -- cgit v1.2.3