Skip to content

Commit cf13e06

Browse files
committed
feat(usart): implement RX #11
close #11
1 parent 0ce082e commit cf13e06

File tree

4 files changed

+99
-7
lines changed

4 files changed

+99
-7
lines changed

src/cpu/cpu.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export interface AVRInterruptConfig {
5353
flagRegister: u16;
5454
flagMask: u8;
5555
constant?: boolean;
56+
inverseFlag?: boolean;
5657
}
5758

5859
export type AVRClockEventCallback = () => void;
@@ -131,7 +132,7 @@ export class CPU implements ICPU {
131132

132133
setInterruptFlag(interrupt: AVRInterruptConfig) {
133134
const { flagRegister, flagMask, enableRegister, enableMask } = interrupt;
134-
if (interrupt.constant) {
135+
if (interrupt.inverseFlag) {
135136
this.data[flagRegister] &= ~flagMask;
136137
} else {
137138
this.data[flagRegister] |= flagMask;

src/peripherals/eeprom.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class AVREEPROM {
7878
enableRegister: this.config.EECR,
7979
enableMask: EERIE,
8080
constant: true,
81+
inverseFlag: true,
8182
};
8283

8384
constructor(

src/peripherals/usart.spec.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ const UDR0 = 0xc6;
1818
// Register bit names
1919
const U2X0 = 2;
2020
const TXEN = 8;
21+
const RXEN = 16;
2122
const UDRIE = 0x20;
2223
const TXCIE = 0x40;
24+
const RXC = 0x80;
2325
const TXC = 0x40;
2426
const UDRE = 0x20;
2527
const USBS = 0x08;
@@ -235,7 +237,20 @@ describe('USART', () => {
235237
});
236238
});
237239

238-
describe('integration', () => {
240+
describe('writeByte', () => {
241+
it('should return false if called when RX is busy', () => {
242+
const cpu = new CPU(new Uint16Array(1024));
243+
const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
244+
cpu.writeData(UCSR0B, RXEN);
245+
cpu.writeData(UBRR0L, 103); // baud: 9600
246+
expect(usart.writeByte(10)).toEqual(true);
247+
expect(usart.writeByte(10)).toEqual(false);
248+
cpu.tick();
249+
expect(usart.writeByte(10)).toEqual(false);
250+
});
251+
});
252+
253+
describe('Integration tests', () => {
239254
it('should set the TXC bit after ~1.04mS when baud rate set to 9600', () => {
240255
const cpu = new CPU(new Uint16Array(1024));
241256
new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
@@ -249,5 +264,27 @@ describe('USART', () => {
249264
cpu.tick();
250265
expect(cpu.data[UCSR0A] & TXC).toEqual(TXC);
251266
});
267+
268+
it('should be ready to recieve the next byte after ~1.04ms when baudrate set to 9600', () => {
269+
const cpu = new CPU(new Uint16Array(1024));
270+
const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ);
271+
const rxCompleteCallback = jest.fn();
272+
usart.onRxComplete = rxCompleteCallback;
273+
cpu.writeData(UCSR0B, RXEN);
274+
cpu.writeData(UBRR0L, 103); // baud: 9600
275+
expect(usart.writeByte(0x42)).toBe(true);
276+
cpu.cycles += 16000; // 1ms
277+
cpu.tick();
278+
expect(cpu.data[UCSR0A] & RXC).toEqual(0); // byte not received yet
279+
expect(usart.rxBusy).toBe(true);
280+
expect(rxCompleteCallback).not.toHaveBeenCalled();
281+
cpu.cycles += 800; // 0.05ms
282+
cpu.tick();
283+
expect(cpu.data[UCSR0A] & RXC).toEqual(RXC);
284+
expect(usart.rxBusy).toBe(false);
285+
expect(rxCompleteCallback).toHaveBeenCalled();
286+
expect(cpu.readData(UDR0)).toEqual(0x42);
287+
expect(cpu.readData(UDR0)).toEqual(0);
288+
});
252289
});
253290
});

src/peripherals/usart.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,31 @@ const UCSRC_UCSZ0 = 0x2; // Character Size 0
6565
const UCSRC_UCPOL = 0x1; // Clock Polarity
6666
/* eslint-enable @typescript-eslint/no-unused-vars */
6767

68+
const rxMasks = {
69+
5: 0x1f,
70+
6: 0x3f,
71+
7: 0x7f,
72+
8: 0xff,
73+
9: 0xff,
74+
};
6875
export class AVRUSART {
6976
public onByteTransmit: USARTTransmitCallback | null = null;
7077
public onLineTransmit: USARTLineTransmitCallback | null = null;
78+
public onRxComplete: (() => void) | null = null;
7179

80+
private rxBusyValue = false;
81+
private rxByte = 0;
7282
private lineBuffer = '';
7383

7484
// Interrupts
85+
private RXC: AVRInterruptConfig = {
86+
address: this.config.rxCompleteInterrupt,
87+
flagRegister: this.config.UCSRA,
88+
flagMask: UCSRA_RXC,
89+
enableRegister: this.config.UCSRB,
90+
enableMask: UCSRB_RXCIE,
91+
constant: true,
92+
};
7593
private UDRE: AVRInterruptConfig = {
7694
address: this.config.dataRegisterEmptyInterrupt,
7795
flagRegister: this.config.UCSRA,
@@ -90,19 +108,29 @@ export class AVRUSART {
90108
constructor(private cpu: CPU, private config: USARTConfig, private freqHz: number) {
91109
this.reset();
92110
this.cpu.writeHooks[config.UCSRA] = (value) => {
93-
cpu.data[config.UCSRA] = value;
94-
cpu.clearInterruptByFlag(this.UDRE, value);
111+
cpu.data[config.UCSRA] = value & (UCSRA_MPCM | UCSRA_U2X);
95112
cpu.clearInterruptByFlag(this.TXC, value);
96113
return true;
97114
};
98115
this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => {
116+
cpu.updateInterruptEnable(this.RXC, value);
99117
cpu.updateInterruptEnable(this.UDRE, value);
100118
cpu.updateInterruptEnable(this.TXC, value);
119+
if (value & UCSRB_RXEN && oldValue & UCSRB_RXEN) {
120+
cpu.clearInterrupt(this.RXC);
121+
}
101122
if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) {
102123
// Enabling the transmission - mark UDR as empty
103124
cpu.setInterruptFlag(this.UDRE);
104125
}
105126
};
127+
this.cpu.readHooks[config.UDR] = () => {
128+
const mask = rxMasks[this.bitsPerChar] ?? 0xff;
129+
const result = this.rxByte & mask;
130+
this.rxByte = 0;
131+
this.cpu.clearInterrupt(this.RXC);
132+
return result;
133+
};
106134
this.cpu.writeHooks[config.UDR] = (value) => {
107135
if (this.onByteTransmit) {
108136
this.onByteTransmit(value);
@@ -116,12 +144,10 @@ export class AVRUSART {
116144
this.lineBuffer += ch;
117145
}
118146
}
119-
const symbolsPerChar = 1 + this.bitsPerChar + this.stopBits + (this.parityEnabled ? 1 : 0);
120-
const cyclesToComplete = (this.UBRR * this.multiplier + 1) * symbolsPerChar;
121147
this.cpu.addClockEvent(() => {
122148
cpu.setInterruptFlag(this.UDRE);
123149
cpu.setInterruptFlag(this.TXC);
124-
}, cyclesToComplete);
150+
}, this.cyclesPerChar);
125151
this.cpu.clearInterrupt(this.TXC);
126152
this.cpu.clearInterrupt(this.UDRE);
127153
};
@@ -131,6 +157,33 @@ export class AVRUSART {
131157
this.cpu.data[this.config.UCSRA] = UCSRA_UDRE;
132158
this.cpu.data[this.config.UCSRB] = 0;
133159
this.cpu.data[this.config.UCSRC] = UCSRC_UCSZ1 | UCSRC_UCSZ0; // default: 8 bits per byte
160+
this.rxBusyValue = false;
161+
this.rxByte = 0;
162+
this.lineBuffer = '';
163+
}
164+
165+
get rxBusy() {
166+
return this.rxBusyValue;
167+
}
168+
169+
writeByte(value: number) {
170+
const { cpu, config } = this;
171+
if (this.rxBusyValue || !(cpu.data[config.UCSRB] & UCSRB_RXEN)) {
172+
return false;
173+
}
174+
this.rxBusyValue = true;
175+
cpu.addClockEvent(() => {
176+
this.rxByte = value;
177+
this.rxBusyValue = false;
178+
cpu.setInterruptFlag(this.RXC);
179+
this.onRxComplete?.();
180+
}, this.cyclesPerChar);
181+
return true;
182+
}
183+
184+
private get cyclesPerChar() {
185+
const symbolsPerChar = 1 + this.bitsPerChar + this.stopBits + (this.parityEnabled ? 1 : 0);
186+
return (this.UBRR * this.multiplier + 1) * symbolsPerChar;
134187
}
135188

136189
private get UBRR() {

0 commit comments

Comments
 (0)