aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals/spi.spec.ts
blob: 635e6575018cd544b11f25b8c5c4c3ddc927e240 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import { CPU } from '../cpu/cpu';
import { AVRSPI, spiConfig } from './spi';
import { asmProgram, TestProgramRunner } from '../utils/test-utils';

const FREQ_16MHZ = 16e6;

// CPU registers
const R17 = 17;
const SREG = 95;

// SPI Registers
const SPCR = 0x4c;
const SPSR = 0x4d;
const SPDR = 0x4e;

// Register bit names
const SPR0 = 1;
const SPR1 = 2;
const CPOL = 4;
const CPHA = 8;
const MSTR = 0x10;
const DORD = 0x20;
const SPE = 0x40;
const SPIE = 0x80;
const WCOL = 0x40;
const SPIF = 0x80;
const SPI2X = 1;

describe('SPI', () => {
  it('should correctly calculate the frequency based on SPCR/SPST values', () => {
    const cpu = new CPU(new Uint16Array(1024));
    const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ);

    // Values in this test are based on Table 19-5 in the datasheet, page 177:
    // http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf

    // Standard SPI speed:
    cpu.writeData(SPSR, 0);
    cpu.writeData(SPCR, 0);
    expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 4);
    cpu.writeData(SPCR, SPR0);
    expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 16);
    cpu.writeData(SPCR, SPR1);
    expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 64);
    cpu.writeData(SPCR, SPR1 | SPR0);
    expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 128);

    // Double SPI speed:
    cpu.writeData(SPSR, SPI2X);
    cpu.writeData(SPCR, 0);
    expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 2);
    cpu.writeData(SPCR, SPR0);
    expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 8);
    cpu.writeData(SPCR, SPR1);
    expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 32);
    cpu.writeData(SPCR, SPR1 | SPR0);
    expect(spi.spiFrequency).toEqual(FREQ_16MHZ / 64);
  });

  it('should correctly report the data order (MSB/LSB first), based on SPCR value', () => {
    const cpu = new CPU(new Uint16Array(1024));
    const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ);

    cpu.writeData(SPCR, 0);
    expect(spi.dataOrder).toBe('msbFirst');

    cpu.writeData(SPCR, DORD);
    expect(spi.dataOrder).toBe('lsbFirst');
  });

  it('should correctly report the SPI mode, based on SPCR value', () => {
    const cpu = new CPU(new Uint16Array(1024));
    const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ);

    // Values in this test are based on Table 2 in the datasheet, page 174.
    cpu.writeData(SPCR, 0);
    expect(spi.spiMode).toBe(0);

    cpu.writeData(SPCR, CPHA);
    expect(spi.spiMode).toBe(1);

    cpu.writeData(SPCR, CPOL);
    expect(spi.spiMode).toBe(2);

    cpu.writeData(SPCR, CPOL | CPHA);
    expect(spi.spiMode).toBe(3);
  });

  it('should indicate slave/master operation, based on SPCR value', () => {
    const cpu = new CPU(new Uint16Array(1024));
    const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ);

    expect(spi.isMaster).toBe(false);

    cpu.writeData(SPCR, MSTR);
    expect(spi.isMaster).toBe(true);
  });

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

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

    expect(spi.onTransfer).toHaveBeenCalledWith(0x8f);
  });

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

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

    expect(spi.onTransfer).not.toHaveBeenCalled();
  });

  it('should transmit a byte successfully (integration)', () => {
    // Based on code example from section 19.2 of the datasheet, page 172
    const { program } = asmProgram(`
      ; register addresses
      _REPLACE SPCR, ${SPCR - 0x20}
      _REPLACE SPDR, ${SPDR - 0x20}
      _REPLACE SPSR, ${SPSR - 0x20}
      _REPLACE DDR_SPI, 0x4 ; PORTB

      SPI_MasterInit:
        ; Set MOSI and SCK output, all others input
        LDI r17, 0x28
        OUT DDR_SPI, r17
    
        ; Enable SPI, Master, set clock rate fck/16
        LDI r17, 0x51   ; (1<<SPE)|(1<<MSTR)|(1<<SPR0)
        OUT SPCR, r17

      SPI_MasterTransmit:
        LDI r16, 0xb8 ; byte to transmit
        OUT SPDR, r16

      Wait_Transmit:
        IN r16, SPSR
        SBRS r16, 7
        RJMP Wait_Transmit
      
      ; Now read the result into r17
        IN r17, SPDR
        BREAK
    `);

    const cpu = new CPU(program);
    const spi = new AVRSPI(cpu, spiConfig, 16e6);

    let byteReceivedFromAsmCode: number | null = null;

    spi.onTransfer = (value) => {
      byteReceivedFromAsmCode = value;
      return 0x5b; // we copy this byte to
    };

    const runner = new TestProgramRunner(cpu);
    runner.runToBreak();

    // 16 cycles per clock * 8 bits = 128
    expect(cpu.cycles).toBeGreaterThanOrEqual(128);

    expect(byteReceivedFromAsmCode).toEqual(0xb8);
    expect(cpu.data[R17]).toEqual(0x5b);
  });

  it('should set the WCOL bit in SPSR if writing to SPDR while SPI is already transmitting', () => {
    const cpu = new CPU(new Uint16Array(1024));
    new AVRSPI(cpu, spiConfig, FREQ_16MHZ);

    cpu.writeData(SPCR, SPE | MSTR);
    cpu.writeData(SPDR, 0x50);
    cpu.tick();
    expect(cpu.readData(SPSR) & WCOL).toEqual(0);

    cpu.writeData(SPDR, 0x51);
    expect(cpu.readData(SPSR) & WCOL).toEqual(WCOL);
  });

  it('should clear the SPIF bit and fire an interrupt when SPI transfer completes', () => {
    const cpu = new CPU(new Uint16Array(1024));
    new AVRSPI(cpu, spiConfig, FREQ_16MHZ);

    cpu.writeData(SPCR, SPE | SPIE | MSTR);
    cpu.writeData(SPDR, 0x50);
    cpu.data[SREG] = 0x80; // SREG: I-------

    // At this point, write shouldn't be complete yet
    cpu.cycles += 10;
    cpu.tick();
    expect(cpu.pc).toEqual(0);

    // 100 cycles later, it should (8 bits * 8 cycles per bit = 64).
    cpu.cycles += 100;
    cpu.tick();
    expect(cpu.data[SPSR] & SPIF).toEqual(0);
    expect(cpu.pc).toEqual(0x22); // SPI Ready interrupt
  });

  it('should should only update SPDR when tranfer finishes (double buffering)', () => {
    const cpu = new CPU(new Uint16Array(1024));
    const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ);
    spi.onTransfer = jest.fn(() => 0x88);

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

    cpu.cycles = 10;
    cpu.tick();
    expect(cpu.readData(SPDR)).toEqual(0);

    cpu.cycles = 32; // 4 cycles per bit * 8 bits = 32
    cpu.tick();
    expect(cpu.readData(SPDR)).toEqual(0x88);
  });
});