Real-time block processing setup on ARM
First, use a timer and an ISR to get accurate timing (don't forget to configure the NVIC so that this timer interrupt takes over any other ISR that would be running). Only this will ensure a consistant sample rate. Little variations in timing would create noticeable degradations of audio quality. In particular, in your current example, unless the "filters" code has no branches, the time at which the SPI_Write occurs will be jittery depending on the path taken in the code.

For block processing, you need a "ping-pong" buffering scheme.

In your per-sample ISR:

SPI_Write(output[active_buffer][sample_index]);
SPI_Read(input[active_buffer][sample_index]);
++sample_index;
if (sample_index &gt;= BUFFER_SIZE) {
  active_buffer = (active_buffer + 1) % NUM_BUFFERS;
  sample_index = 0;
}


In your main code:

while (render_buffer != active_buffer) {
  // Process samples from input[render_buffer][0] to input[render_buffer][BUFFER_SIZE - 1]
  // And write them to output[render_buffer][0] to output[render_buffer][BUFFER_SIZE - 1]
  render_buffer = (render_buffer + 1) % NUM_BUFFERS;
}


render_buffer and active_buffer are declared as volatile. Start with NUM_BUFFERS equal to 2. Higher values will introduce more latency, but might be necessary if the processing code has exceptionally long code paths.

The benefit of processing samples block by block is that some variables required by your DSP algorithm (such as filter state variables) can be kept in registers within the processing loop, saving load/stores. There are also situations in which you can benefit from batch load/stores. Finally, it makes it easier to schedule computations at "audio rate" (once per audio sample) and "control rate" (once per block).