Skip to content

Commit ec1346b

Browse files
authored
feat(adc): ADC peripheral #13
1 parent 39fe047 commit ec1346b

File tree

3 files changed

+405
-0
lines changed

3 files changed

+405
-0
lines changed

src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77
export { CPU, ICPU, CPUMemoryHook, CPUMemoryHooks } from './cpu/cpu';
88
export { avrInstruction } from './cpu/instruction';
99
export { avrInterrupt } from './cpu/interrupt';
10+
export {
11+
ADCConfig,
12+
adcConfig,
13+
ADCMuxConfiguration,
14+
ADCMuxInput,
15+
ADCMuxInputType,
16+
ADCReference,
17+
atmega328Channels,
18+
AVRADC,
19+
} from './peripherals/adc';
1020
export {
1121
AVRTimer,
1222
AVRTimerConfig,

src/peripherals/adc.spec.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { CPU } from '../cpu/cpu';
2+
import { asmProgram, TestProgramRunner } from '../utils/test-utils';
3+
import { AVRADC, adcConfig, ADCMuxInputType } from './adc';
4+
5+
const R16 = 16;
6+
const R17 = 17;
7+
8+
const ADMUX = 0x7c;
9+
const REFS0 = 1 << 6;
10+
11+
const ADCSRA = 0x7a;
12+
const ADEN = 1 << 7;
13+
const ADSC = 1 << 6;
14+
const ADPS0 = 1 << 0;
15+
const ADPS1 = 1 << 1;
16+
const ADPS2 = 1 << 2;
17+
18+
const ADCH = 0x79;
19+
const ADCL = 0x78;
20+
21+
describe('ADC', () => {
22+
it('should successfuly perform an ADC conversion', () => {
23+
const { program } = asmProgram(`
24+
; register addresses
25+
_REPLACE ADMUX, ${ADMUX}
26+
_REPLACE ADCSRA, ${ADCSRA}
27+
_REPLACE ADCH, ${ADCH}
28+
_REPLACE ADCL, ${ADCL}
29+
30+
; Configure mux - channel 0, reference: AVCC with external capacitor at AREF pin
31+
ldi r24, ${REFS0}
32+
sts ADMUX, r24
33+
34+
; Start conversion with 128 prescaler
35+
ldi r24, ${ADEN | ADSC | ADPS0 | ADPS1 | ADPS2}
36+
sts ADCSRA, r24
37+
38+
; Wait until conversion is complete
39+
waitComplete:
40+
lds r24, ${ADCSRA}
41+
andi r24, ${ADSC}
42+
brne waitComplete
43+
44+
; Read the result
45+
lds r16, ${ADCL}
46+
lds r17, ${ADCH}
47+
48+
break
49+
`);
50+
const cpu = new CPU(program);
51+
const adc = new AVRADC(cpu, adcConfig);
52+
const runner = new TestProgramRunner(cpu);
53+
54+
const adcReadSpy = jest.spyOn(adc, 'onADCRead');
55+
adc.channelValues[0] = 2.56; // should result in 2.56/5*1024 = 524
56+
57+
// Setup
58+
runner.runInstructions(4);
59+
expect(adcReadSpy).toHaveBeenCalledWith({ channel: 0, type: ADCMuxInputType.SingleEnded });
60+
61+
// Run the "waitComplete" loop for a few cycles
62+
runner.runInstructions(12);
63+
64+
cpu.cycles += 128 * 25; // skip to the end of the conversion
65+
cpu.tick();
66+
67+
// Now read the result
68+
runner.runInstructions(5);
69+
70+
const low = cpu.data[R16];
71+
const high = cpu.data[R17];
72+
expect((high << 8) | low).toEqual(524); // 2.56 volts - see above
73+
});
74+
75+
it('should read 0 when the ADC peripheral is not enabled', () => {
76+
// This behavior was verified on real hardware, using the following test program:
77+
// https://wokwi.com/arduino/projects/309156042450666050
78+
// Thanks Oscar Oomens for spotting this!
79+
80+
const { program } = asmProgram(`
81+
; register addresses
82+
_REPLACE ADMUX, ${ADMUX}
83+
_REPLACE ADCSRA, ${ADCSRA}
84+
_REPLACE ADCH, ${ADCH}
85+
_REPLACE ADCL, ${ADCL}
86+
87+
; Load some initial value into r16/r17 to make sure we actually read 0 later
88+
ldi r16, 0xff
89+
ldi r17, 0xff
90+
91+
; Configure mux - channel 0, reference: AVCC with external capacitor at AREF pin
92+
ldi r24, ${REFS0}
93+
sts ADMUX, r24
94+
95+
; Start conversion with 128 prescaler, but without enabling the ADC
96+
ldi r24, ${ADSC | ADPS0 | ADPS1 | ADPS2}
97+
sts ADCSRA, r24
98+
99+
; Wait until conversion is complete
100+
waitComplete:
101+
lds r24, ${ADCSRA}
102+
andi r24, ${ADSC}
103+
brne waitComplete
104+
105+
; Read the result
106+
lds r16, ${ADCL}
107+
lds r17, ${ADCH}
108+
109+
break
110+
`);
111+
const cpu = new CPU(program);
112+
const adc = new AVRADC(cpu, adcConfig);
113+
const runner = new TestProgramRunner(cpu, () => {
114+
/* do nothing on break */
115+
});
116+
117+
const adcReadSpy = jest.spyOn(adc, 'onADCRead');
118+
adc.channelValues[0] = 2.56; // should result in 2.56/5*1024 = 524
119+
120+
// Setup
121+
runner.runInstructions(6);
122+
expect(adcReadSpy).not.toHaveBeenCalled();
123+
124+
// Run the "waitComplete" loop for a few cycles
125+
runner.runInstructions(12);
126+
127+
cpu.cycles += 128 * 25; // skip to the end of the conversion
128+
cpu.tick();
129+
130+
// Now read the result
131+
runner.runToBreak();
132+
133+
const low = cpu.data[R16];
134+
const high = cpu.data[R17];
135+
expect((high << 8) | low).toEqual(0); // We should read 0 since the ADC hasn't been enabled
136+
});
137+
});

0 commit comments

Comments
 (0)