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

  • On what board...? – Majenko May 16 '18 at 10:27
  • @Majenko edited, Arduino Nano – Zahi Azmi May 16 '18 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/…) – Maximilian Gerhardt May 16 '18 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 – Zahi Azmi May 16 '18 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. – Edgar Bonet May 16 '18 at 17:10

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.

| improve this answer | |

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()
    for (size_t i = 0; i < sizeof buffer; i++)
        buffer[i] = PINB;

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:

    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

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()
    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
        ::      "z" (buffer),
        [count] "i" (sizeof buffer),
        [pin]   "I" (_SFR_IO_ADDR(PINB))
        : "r0"

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.

| improve this answer | |

Not the answer you're looking for? Browse other questions tagged or ask your own question.