Oscilloscope with 1.8" TFT, somewhat faster, using an ISR


Most ATmega328-based oscilloscopes only work up to 153846 ( = 16,000,000 / 8 / 13) samples per second or even less.
One way to increase the speed in which the ADC is read is making use of the ADC interrupt. Therefore, you have to implement an interrupt service routine like this

ISR(ADC_vect) {
  index--;
  if (index == 0) ADCSRA = 0;
  // disable this ISR, so it won't be called again
  x[index] = ADCH;  // read 8 bit value from ADC
}

(By running though the array backwards you benefit from the advantage avoiding to compare the index with a constant which saves you a lot of bytes.)
The loop() function only needs to do its work when ADCSRC-register is equal to zero.

The ATMEL documentation informs about the conversion speeds:

Table 23-5. ADC Prescaler Selections
ADPS2 ADPS1 ADPS0 Division Factor Clock freq [MHz] Sampling rate [kHz]
0 0 0 2 8 615
0 0 1 2 8 615
0 1 0 4 4 307
0 1 1 8 2 153
1 0 0 16 1 78.8
1 0 1 32 0.5 38.4
1 1 0 64 0.25 19.2
1 1 1 128 0.125 9.6

While division factors as 8 and above work pretty well division factors 4 and below do not. The question arises: why don't they?

When you take a look at the code produced by the compiler (using avr-objdump.exe) you will find that the value of "index" is read three times (see table shown at the end). And therefore, more registers have to be saved and restored. So it would be a good idea to code the assembler instructions by hand.

Thanks to helpful discussions with Heiko P. it turned out that the declaration

prevents the compiler to make use of any optimizations. But as both, the loop and the ISR have to access index you cannot avoid the volatile qualifier.

If you code it manually you are taking over the responsibility completely, so take care. By the way: as soon as you decide to code in assembler you can drop the volatile.

But there is one more problem: of course, you need access to the address of the array x[]. And to do this is somewhat tricky because the ATMEL controllers are RISC controllers, and, honestly, they are very riscy. ATMEL did not provide add immediately and adc immediately instructions. Instead, you have to take the subtract instructions using the two's complements of the operand. But how to insert the two's complements byte by byte? Well, at the end of the day, I was totally surprised to find out it was incredibly easy: just use the minus sign (-).

Still, the speed problem was not solved completely; at times, the program just halted. So, a watchdog was added. A counter which survived the watchdog restarts by using the noinit section was implemented and showed the number of restarts since power-up. This worked quite nice, but you would not like your oscilloscope to restart every now and then. It turned out that the Timer0 interrupt disturbed the ADC interrupt. So, setting TIMKS0 to zero fixed the problem. Of course, the millis() and micros()-functions do not work while the ISR is active.

To obtain different speeds the prescaler can be modified by inserting a jumper to the pins 2 to 7. As I used the 1.8" TFT display giving a width of 160 pixels the total sampling time for one screen is given by the time per sample multiplied by 160 (actually by 158 because of the frame lines) resulting in values from 0.5 milliseconds to 4 milliseconds.

So, the oscilloscope runs pretty well up to input signals of some 25 kHz. Increasing that frequency the watchdog will restart the scope at times.

If you want to check the difference between just have a close look at this comparison:

compiler generated manually assembled
ISR(ADC_vect) {
  index--;
  if (index == 0) ADCSRA = 0;
  // disable this ISR, so it won't be called again
  x[index] = ADCH;  // read 8 bit value from ADC
}



00001464 <__vector_21>:
    1464:	1f 92       	push	r1
    1466:	0f 92       	push	r0
    1468:	0f b6       	in	r0, 0x3f
    146a:	0f 92       	push	r0
    146c:	11 24       	eor	r1, r1
    146e:	8f 93       	push	r24
    1470:	9f 93       	push	r25
    1472:	ef 93       	push	r30
    1474:	ff 93       	push	r31
  index--;
    1476:	80 91 00 01 	lds	r24, 0x0100
    147a:	90 91 01 01 	lds	r25, 0x0101
    147e:	01 97       	sbiw	r24, 0x01	
    1480:	90 93 01 01 	sts	0x0101, r25
    1484:	80 93 00 01 	sts	0x0100, r24
  if (index == 0) ADCSRA = 0;
    1488:	80 91 00 01 	lds	r24, 0x0100
    148c:	90 91 01 01 	lds	r25, 0x0101
    1490:	89 2b       	or	r24, r25
    1492:	11 f4       	brne	.+4   
    1494:	10 92 7a 00 	sts	0x007A, r1
  // disable this ISR, so it won't be called again
  x[index] = ADCH;  // read 8 bit value from ADC
    1498:	e0 91 00 01 	lds	r30, 0x0100
    149c:	f0 91 01 01 	lds	r31, 0x0101
    14a0:	80 91 79 00 	lds	r24, 0x0079
    14a4:	e8 5b       	subi	r30, 0xB8
    14a6:	fd 4f       	sbci	r31, 0xFD
    14a8:	80 83       	st	Z, r24
    14aa:	ff 91       	pop	r31
    14ac:	ef 91       	pop	r30
    14ae:	9f 91       	pop	r25
    14b0:	8f 91       	pop	r24
    14b2:	0f 90       	pop	r0
    14b4:	0f be       	out	0x3f, r0
    14b6:	0f 90       	pop	r0
    14b8:	1f 90       	pop	r1
    14ba:	18 95       	reti
ISR(ADC_vect, ISR_NAKED) {
  asm( // code:
    "X" (index),      // %0
    "X" (ADCH),       // %1
    "X" (ADCSRA),     // %2
    "X" (x)           // %3
    : // list of used registers:
    "r24", "r30", "r31"
  );
000013d0 <__vector_21>:
    13d0:	8f 93       	push	r24
    13d2:	8f b7       	in	r24, 0x3f
    13d4:	8f 93       	push	r24
    13d6:	ef 93       	push	r30
    13d8:	ff 93       	push	r31





    13da:	e0 91 00 01 	lds	r30, 0x0100
    13de:	f0 91 01 01 	lds	r31, 0x0101
    13e2:	31 97       	sbiw	r30, 0x01
    13e4:	e0 93 00 01 	sts	0x0100, r30
    13e8:	f0 93 01 01 	sts	0x0101, r31




    13ec:	11 f4       	brne	.+4  
    13ee:	e0 93 7a 00 	sts	0x007A, r30




    13f2:	80 91 79 00 	lds	r24, 0x0079
    13f6:	e4 52       	subi	r30, 0x24
    13f8:	fe 4f       	sbci	r31, 0xFE
    13fa:	80 83       	st	Z, r24



    13fc:	ff 91       	pop	r31
    13fe:	ef 91       	pop	r30
    1400:	8f 91       	pop	r24
    1402:	8f bf       	out	0x3f, r24
    1404:	8f 91       	pop	r24
    1406:	18 95       	reti

Download the source




contact: nji(at)gmx.de