Skip to content

Commit 64736a7

Browse files
committed
feat(spi): add onByte callback
a more versatile alternative to the `onTransfer` callback. Depracate `onTransfer()`.
1 parent 0f6385d commit 64736a7

File tree

2 files changed

+44
-19
lines changed

2 files changed

+44
-19
lines changed

src/peripherals/spi.spec.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,26 +96,26 @@ describe('SPI', () => {
9696
expect(spi.isMaster).toBe(true);
9797
});
9898

99-
it('should call the `onTransfer` callback when initiating an SPI trasfer by writing to SPDR', () => {
99+
it('should call the `onByteTransfer` callback when initiating an SPI trasfer by writing to SPDR', () => {
100100
const cpu = new CPU(new Uint16Array(1024));
101101
const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ);
102-
spi.onTransfer = jest.fn();
102+
spi.onByte = jest.fn();
103103

104104
cpu.writeData(SPCR, SPE | MSTR);
105105
cpu.writeData(SPDR, 0x8f);
106106

107-
expect(spi.onTransfer).toHaveBeenCalledWith(0x8f);
107+
expect(spi.onByte).toHaveBeenCalledWith(0x8f);
108108
});
109109

110110
it('should ignore SPDR writes when the SPE bit in SPCR is clear', () => {
111111
const cpu = new CPU(new Uint16Array(1024));
112112
const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ);
113-
spi.onTransfer = jest.fn();
113+
spi.onByte = jest.fn();
114114

115115
cpu.writeData(SPCR, MSTR);
116116
cpu.writeData(SPDR, 0x8f);
117117

118-
expect(spi.onTransfer).not.toHaveBeenCalled();
118+
expect(spi.onByte).not.toHaveBeenCalled();
119119
});
120120

121121
it('should transmit a byte successfully (integration)', () => {
@@ -155,9 +155,9 @@ describe('SPI', () => {
155155

156156
let byteReceivedFromAsmCode: number | null = null;
157157

158-
spi.onTransfer = (value) => {
158+
spi.onByte = (value) => {
159159
byteReceivedFromAsmCode = value;
160-
return 0x5b; // we copy this byte to
160+
cpu.addClockEvent(() => spi.completeTransfer(0x5b), spi.transferCycles);
161161
};
162162

163163
const runner = new TestProgramRunner(cpu, () => 0);
@@ -228,7 +228,9 @@ describe('SPI', () => {
228228
it('should should only update SPDR when tranfer finishes (double buffering)', () => {
229229
const cpu = new CPU(new Uint16Array(1024));
230230
const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ);
231-
spi.onTransfer = jest.fn(() => 0x88);
231+
spi.onByte = () => {
232+
cpu.addClockEvent(() => spi.completeTransfer(0x88), spi.transferCycles);
233+
};
232234

233235
cpu.writeData(SPCR, SPE | MSTR);
234236
cpu.writeData(SPDR, 0x8f);

src/peripherals/spi.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,28 @@ export const spiConfig: SPIConfig = {
3131
SPDR: 0x4e,
3232
};
3333

34-
export type SPITransferCallback = (value: u8) => u8;
34+
export type SPITransferCallback = (value: u8) => number;
35+
export type SPIByteTransferCallback = (value: u8) => void;
3536

3637
const bitsPerByte = 8;
3738

3839
export class AVRSPI {
39-
public onTransfer: SPITransferCallback | null = null;
40+
/** @deprecated Use onByte() instead */
41+
public onTransfer: SPITransferCallback = () => 0;
42+
43+
/**
44+
* SPI byte transfer callback. Invoked whenever the user code starts an SPI transaction.
45+
* You can override this with your own SPI handler logic.
46+
*
47+
* The callback receives a argument: the byte sent over the SPI MOSI line.
48+
* It should call `completeTransfer()` within `transferCycles` CPU cycles.
49+
*/
50+
public onByte: SPIByteTransferCallback = (value) => {
51+
const valueIn = this.onTransfer(value);
52+
this.cpu.addClockEvent(() => this.completeTransfer(valueIn), this.transferCycles);
53+
};
4054

4155
private transmissionActive = false;
42-
private receivedByte: u8 = 0;
4356

4457
// Interrupts
4558
private SPI: AVRInterruptConfig = {
@@ -68,14 +81,8 @@ export class AVRSPI {
6881
cpu.data[SPSR] &= ~SPSR_WCOL;
6982
this.cpu.clearInterrupt(this.SPI);
7083

71-
this.receivedByte = this.onTransfer?.(value) ?? 0;
72-
const cyclesToComplete = this.clockDivider * bitsPerByte;
7384
this.transmissionActive = true;
74-
this.cpu.addClockEvent(() => {
75-
this.cpu.data[SPDR] = this.receivedByte;
76-
this.cpu.setInterruptFlag(this.SPI);
77-
this.transmissionActive = false;
78-
}, cyclesToComplete);
85+
this.onByte(value);
7986
return true;
8087
};
8188
cpu.writeHooks[SPCR] = (value: u8) => {
@@ -89,7 +96,18 @@ export class AVRSPI {
8996

9097
reset() {
9198
this.transmissionActive = false;
92-
this.receivedByte = 0;
99+
}
100+
101+
/**
102+
* Completes an SPI transaction. Call this method only from the `onByte` callback.
103+
*
104+
* @param receivedByte Byte read from the SPI MISO line.
105+
*/
106+
completeTransfer(receivedByte: number) {
107+
const { SPDR } = this.config;
108+
this.cpu.data[SPDR] = receivedByte;
109+
this.cpu.setInterruptFlag(this.SPI);
110+
this.transmissionActive = false;
93111
}
94112

95113
get isMaster() {
@@ -128,6 +146,11 @@ export class AVRSPI {
128146
throw new Error('Invalid divider value!');
129147
}
130148

149+
/** Number of cycles to complete a single byte SPI transaction */
150+
get transferCycles() {
151+
return this.clockDivider * bitsPerByte;
152+
}
153+
131154
/**
132155
* The SPI freqeuncy is only relevant to Master mode.
133156
* In slave mode, the frequency can be as high as F(osc) / 4.

0 commit comments

Comments
 (0)