Skip to content

Commit

Permalink
feat(usart): implement RX #11
Browse files Browse the repository at this point in the history
close #11
  • Loading branch information
urish committed Feb 19, 2021
1 parent 0ce082e commit cf13e06
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 7 deletions.
3 changes: 2 additions & 1 deletion src/cpu/cpu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface AVRInterruptConfig {
flagRegister: u16;
flagMask: u8;
constant?: boolean;
inverseFlag?: boolean;
}

export type AVRClockEventCallback = () => void;
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/peripherals/eeprom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class AVREEPROM {
enableRegister: this.config.EECR,
enableMask: EERIE,
constant: true,
inverseFlag: true,
};

constructor(
Expand Down
39 changes: 38 additions & 1 deletion src/peripherals/usart.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
});
});
});
63 changes: 58 additions & 5 deletions src/peripherals/usart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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);
};
Expand All @@ -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() {
Expand Down

0 comments on commit cf13e06

Please sign in to comment.