1

I have an ADC that have sampling rate up to 40 MSPS, but i use 8 MHz clock so it will be 8 MSPS. The board used is arduino nano. The problem is i have to sampling it just 2 MHz (because the capacity of array variable in arduino Nano, which is around 10.000 elements in array variable). I know that using direct port reading will increase the speed of the digital read, but how can i set the direct port reading to just read every 0.5 us? Micros function cannot do the job of course

Thank you

asked May 16, 2018 at 10:21
6
  • On what board...? Commented May 16, 2018 at 10:27
  • @Majenko edited, Arduino Nano Commented May 16, 2018 at 10:33
  • 1
    You don't set the sampling rate for a digital pin. You sample it in software by readinging the appropiate PORTX register (C,D,..). 500 nanoseconds is pretty on the edge. Only reading the PORT will cost you 125ns (and that's NOT doing any SPI logic -- if you bitbang SPI then this will be ONE BIT!) (github.com/NicksonYap/digitalWriteFast/blob/master/NOTES/…) Commented May 16, 2018 at 10:33
  • @MaximilianGerhardt how can i set it in the software? AFAIK, using timer wont work because there is no timer with resolution 500ns Commented May 16, 2018 at 10:37
  • You can sample a digital port at 2 MHz, or even as fast as F_CPU/3, while storing the data in an array, but: 1. This needs assembly programming and, 2. It is very likely not what you want to achieve. Commented May 16, 2018 at 17:10

2 Answers 2

5

You need:

  1. A timer
  2. A board that is capable of running fast enough to do what you want.

The Nano fits the first criteria, but not the second.

At 16MHz you get a clock period of 63ns. That means you get a mere 8 clock ticks (actually gives you 504ns) per interruption from the timer. That's at most 8 assembly instructions that can be executed within that time.

Since an interrupt service routine's preamble is longer than 7 instructions you can see there would be a slight problem there. No time for actually reading a digital IO port - and if you are using a serial protocol to communicate with your ADC you can multiply the reading time by the double the number of bits you need to read. That's a long time compared to 500ns.

So no. Think again. Use a more suitable board. Maybe an ARM based board (like the Teensy 3.x) would be a better fit.

answered May 16, 2018 at 10:40
3

Note: this is an attempt to answer the question as asked. This answer is unlikely to be of any use to the original poster, who presumably asked the wrong question. I am writing this only as a way to explore the limits on how fast a modest AVR can sample a port. For an answer that genuinely attempts to address the OP’s problem, see Majenko’s answer.

I read the question as follows: can we sample a digital port at 2 MHz on an Arduino Nano clocked at 8 MHz? Can we do so while storing the values in a RAM-based buffer?

The answer is yes, but it is non trivial, and it requires some assembly. To see the problem, let’s start by trying to do it in C++:

uint8_t buffer[1024];
void fill_buffer()
{
 cli();
 for (size_t i = 0; i < sizeof buffer; i++)
 buffer[i] = PINB;
 sei();
}

Note that the loop runs with interrupts disabled, otherwise the timer interrupt would wreak havoc with the loop timing. This is translated by gcc into an assembly equivalent to this:

 cli
 ldi r30, lo8(buffer) ; load the buffer address into pointer Z
 ldi r31, hi8(buffer) ; ditto
0: in r24, 0x03 ; read the port
 st Z+, r24 ; store into buffer, increment the pointer
 ldi r24, hi8(buffer+1024) ; save (buffer+1024)>>8 in r24
 cpi r30, lo8(buffer+1024) ; compare the pointer with buffer+1024
 cpc r31, r24 ; ditto
 brne 0b ; loop back
 sei
 ret

The loop takes 8 cycles per iteration. With an 8 MHz clock, that would be one reading per microsecond. Too slow by a factor two.

One could save one cycle by using a different register for the port data and for the end-of-loop condition, and by moving the third ldi out of the loop. Another cycle could be saved by testing only the high byte of the Z pointer, but that would require aligning the buffer to 256 byte boundaries. With those two optimizations, we still need 6 CPU cycles per iteration, i.e. 0.75 μs at 8 MHz.

In order to make this faster, the only solution is to unroll the loop. This can be done in assembly by using the .rept (meaning "repeat") directive:

void fill_buffer()
{
 cli();
 asm volatile(
 ".rept %[count]\n" // repeat (count) times:
 "in r0, %[pin]\n" // read the port input register
 "st Z+, r0\n" // store in RAM
 "nop\n" // 1 cycle delay
 ".endr"
 :: "z" (buffer),
 [count] "i" (sizeof buffer),
 [pin] "I" (_SFR_IO_ADDR(PINB))
 : "r0"
 );
 sei();
}

This takes 4 cycles, or 0.5 μs per iteration. Note that a delay cycle had to be introduced, otherwise the sampling would be too fast : 3 cycles, or 0.375 μs, per iteration.

This is not the fastest one can get. It is possible to take one sample per CPU cycle with something like this:

 in r0, 0x03
 in r1, 0x03
 in r2, 0x03
 ...

However this technique is limited to burst readings of at most 32 samples.

answered May 16, 2018 at 20:40

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.