Faster SPI communication?

See this thread, reply #36 (TurboSpi 42 MHz):

https://forum.arduino.cc/index.php?topic=437243.30

You can have several SPI slaves connected on the same SPI bus, but the SPI Master will not address all the slaves exactely at the same time.

BTW, you can have two more SPI buses on USART0 and USART1, but you will have to program your own code since SPI Library is only for the ICSP.

PieterP:
You'll probably want to look into DMA (direct memory access).
AFAIK, you can't use it "at the same time", because every time the SD card's CS pin goes high, the file is closed.

Pieter

Greetings,

Thank you for clarifing this question.

So that means you can't save samples to a file using an external SPI ADC, or read samples and send them to an external SPI DAC?

I need for example an external SPI RAM chip?
If I use one I could copy the contents of the file and then read them from the RAM?
And I could save the samples from the ADC to the RAM and then write them in the SD card.
The problem is I need a very large RAM to store the files (about 50-100 MB).

Regards,
Daniel

Please understand that (in your case) SPI limits the maximum transfer rate, not the connected devices.

For best throughput you should use different (independent) transmission devices or protocols, like Serial or I2C in addition to SPI.

Is it possible to use both DAC channels in free running mode on Arduino Due?

danny92:
The TurboSPI library functions should be faster, but they are actually slower compared to those of the SPI library.

Wrong

danny92:
So that means you can't save samples to a file using an external SPI ADC, or read samples and send them to an external SPI DAC?

No

danny92:
Is it possible to use both DAC channels in free running mode on Arduino Due?

Yes - With a DUE clocked at 84 MHz, the DACs output frequency in free running mode is 1.68 MHz.

ard_newbie:
Wrong

I've tested both libraries using micros() and the turboSPI library was actually slower. What Am I doing wrong?

ard_newbie:
No

PieterP:
You'll probably want to look into DMA (direct memory access).
AFAIK, you can't use it "at the same time", because every time the SD card's CS pin goes high, the file is closed.

SD card uses SPI and requires the CS to be low, otherwise the file will close, unless you use a huge external RAM, reading or writing directly to or from the SD from a ADC or to an DAC, seems impossible. Am I wrong?

ard_newbie:
Yes - With a DUE clocked at 84 MHz, the DACs output frequency in free running mode is 1.68 MHz.

The problem is I have no idea how to do that.

In theory you could wire the ADC MISO to the SD card MOSI, and the SD card MISO to the DAC MOSI. Then you can transfer data from the ADC to the SD card, and from there to the DAC, by enabling (CS) the right devices at the same time. In practice all MOSI are tied together, and all MISO, and all data transfers go between a device and memory.

danny92:
The problem is I have no idea how to do that.

See this thread, reply #8 for an example sketch of an output on DAC 1 from an input ADC conversion:

https://forum.arduino.cc/index.php?topic=527853.0

A tip to write your own code, read DAC section of Sam3x datasheet.

Thank you so much. I've seen the thread you suggested, and I've read the DAC part of the datasheet, it's interesting the things you can do if you know more about the hardware you're using.

Anyway, I'm using the DAC in free running mode but there are 3 strange things:

  • 1st -> if I don't disable interrupts after setting the DAC registers the voltage values are not full swing, I'm only getting values between 0.9 V and 2.3 V on both channels
  • 2nd -> even disabling interrupts after setting DAC values, the voltage swing is only between 0.6 V and 2.65 V on channel 1 (I've checked with analogWrite and it should be betwen 0.55 V and 2.71 V), on channel 0 is spot on (0.55 V to 2.71 V)
  • 3rd -> my code still doesnt' work with 44100 Hz sampled wave files, only 32000Hz and I've no idea why this happens, because the DAC values are changed within 2-3 microseconds, I think there's something wrong with my code. There's no way I can find a solution to this. :frowning:

Could you help me finding the bug! :stuck_out_tongue:

I also want to try a library I found, made by TMRh20, that implements a SPI using the UART, do you know if this library really works as supposed? Any examples? I want to try my code using an external DAC.

Links to the library

DumpAudioFile3_optimized_dma.ino (17.9 KB)

DumpAudioFile4_optimized_dma.ino (19.2 KB)

sketch_mar07c.ino (2.15 KB)

You can't use the DAC peripheral in free running mode AND have a frequency conversion of 44.1 KHz ! you have to choose between free running mode and hardware trigger.

Here is an example sketch to trigger ADC conversions with a 44.1 KHz frequency. You can easily adapt it for DAC conversions :

/******************************************************************/
/*      ADC conversions triggered by a Timer Counter 0 Channel 2  */
/*                Frequency = 44.1 KHz                            */
/******************************************************************/
volatile uint16_t LastValueRead;
volatile boolean FlagReadAvailable;
const uint32_t BufferSize = 1000;    
uint16_t Buffer[BufferSize];        // For 12_bit conversions

void setup()
{
  Serial.begin(250000);
  adc_setup();
  tc_setup();
}

void loop()
{
  uint32_t Index;
  Index = 0;
  printf(" Start of %d conversions\n", BufferSize);
  while (Index < BufferSize) {
    if (FlagReadAvailable == true) {
      FlagReadAvailable = false;
      Buffer[Index] = LastValueRead;
      Index++;
    }
  }
  printf(" End of %d conversions\n", BufferSize);
  delay(1000);
}

/*************  Configure ADC function  *******************/
void adc_setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power on
  ADC->ADC_CR = ADC_CR_SWRST;                           // Reset ADC
  ADC->ADC_MR |=  ADC_MR_TRGEN_EN                       // Hardware trigger select
                  | ADC_MR_TRGSEL_ADC_TRIG3;            // Trigger by TIOA2


  ADC->ADC_IER = ADC_IER_EOC7;                         // Interrupt on End of Conversion channel 7
  ADC->ADC_CHER = ADC_CHER_CH7;                        // Enable ADC CH7 = A0

  NVIC_EnableIRQ(ADC_IRQn);                            // Enable ADC interruptions

}

void ADC_Handler(void) {
  LastValueRead = ADC->ADC_CDR[7];   // Read ADC conversion of channel 7
  FlagReadAvailable = true;
}

/*************  Timer Counter 0 Channel 2 to generate PWM pulses thru TIOA2  ************/
void tc_setup() {

  PMC->PMC_PCER0 |= PMC_PCER0_PID29;                      // TC2 power ON : Timer Counter 0 channel 2 IS TC2
  TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2  // MCK/8, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC        // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR          // Clear TIOA2 on RA compare match
                              | TC_CMR_ACPC_SET;           // Set TIOA2 on RC compare match


  TC0->TC_CHANNEL[2].TC_RC = 238;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 44.117 Hz
  TC0->TC_CHANNEL[2].TC_RA = 40;  //<********************   Any Duty cycle in between 1 and 238

  TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable

}

Plus you are not using the TAG option properly to output conversions on DAC0 and DAC1. This should be something like this :

uint16_t val0, val1;
val0 = val0 & 0xFFF;  // 12-bit value
val1 = val1 & 0xFFF;
uint32_t ValToDac;    // 32-bit value to convert on DAC0 and DAC1

// Bits 12 and 28 select the DAC outputs
// Note that (0 << 28) is not useful here
uint32_t chsel = (1 << 12) | (0 << 28);   

// The low part will be converted on DAC1, the high part on DAC0
ValToDac = val1 << 16 | val0 | chsel;

For a full swing of your builtin DACs, see this tutorial:

If you want to use an external DAC, Google DAC DUE SPI and you will find a relevant .pdf from chianorobotics.

Greetings

Thank you for the examples, but I don't understand how the DAC handler function is called, neither what name should it have, I can only hear static, the DAC is not running at all, what Am I doing wrong? Could you please take a look at my code? I'm confused. :confused:

I'm still having the voltage swing issues, even using your example code, could you test it, and see if it also happens with your arduino?

ard_newbie:
If you want to use an external DAC, Google DAC DUE SPI and you will find a relevant .pdf from chianorobotics.

Thank you, but I've already tried that configuration, and I've those DACs, the MCP4922, but the problem is, I can't use them when I'm reading a file from the SD card unless I use a software defined SPI or UART SPI, because the SD card's CS can't go low without closing the file. Any suggestions to solve this problem, I don't know much about software SPI or UART SPI.

Wav file reader code that it's not working: DumpAudioFile5_optimized_dma.ino
Voltage swing not reaching limits on channel 1: sketch_mar08a.ino

Regards,
Daniel

DumpAudioFile5_optimized_dma.ino (22.7 KB)

sketch_mar08a.ino (2.47 KB)

I did some modifications in your sketch to show DACs correct behavior (always attach a 1 or 2 K resistor in serie to each DAC !):

volatile uint16_t dac0Value = 0;
volatile uint16_t dac1Value = 4095;

void dac_setup () {

  PMC->PMC_PCER1 = PMC_PCER1_PID38;     // DACC power ON

  DACC->DACC_CR = DACC_CR_SWRST ;       // Reset DACC

  DACC->DACC_MR = DACC_MR_TRGEN_DIS                   // Free running mode
                  | DACC_MR_TAG_EN                    // enable TAG to set channel in CDR
                  | DACC_MR_WORD_WORD                 // write to both channels
                  | DACC_MR_REFRESH (1)
                  | DACC_MR_STARTUP_8
                  | DACC_MR_MAXS;

  DACC->DACC_IER |= DACC_IER_EOC;                     // Interrupt on End Of Conversion

  // setup analog current
  DACC->DACC_ACR = DACC_ACR_IBCTLCH0(0b10)
                   | DACC_ACR_IBCTLCH1(0b10)
                   | DACC_ACR_IBCTLDACCORE(0b01);

  NVIC_EnableIRQ(DACC_IRQn);               // Enable DACC interrupt

  DACC->DACC_CHER = DACC_CHER_CH0          // enable channel 0 = DAC0
                    | DACC_CHER_CH1;       // enable channel 1 = DAC1
}

void DACC_Handler() {
  uint32_t ValToDac;    // 32-bit value to convert on DAC0 and DAC1

  // Bits 12 and 28 select the DAC outputs
  // Note that (0 << 28) is not useful here
  const uint32_t chsel = (1 << 12) | (0 << 28);

 // Reading DACC_ISR clears the EOC bit
 // enabling DACC_Handler() to be triggered again
  DACC->DACC_ISR; 

  // This can be done elsewhere in loop() ******
  //dac0Value &= 0x0FFF;  // 12-bit value
  //dac1Value &= 0x0FFF;

  // The low part will be converted on DAC1, the high part on DAC0
  ValToDac = dac0Value << 16 | dac1Value | chsel;
  DACC->DACC_CDR = ValToDac;
}

void setup() {
  //Serial.begin(250000); // Always use the maximum output baudrate
  // while (!Serial);  // Useless with Serial, useful with SerialUSB
  dac_setup();
  
  // DACC_Handler(); // Don't call DACC_Handler () "manually" ***************
}


void loop() {

  dac0Value &= 0x0FFF;  // 12-bit value
  dac1Value &= 0x0FFF;
}

Some SD card devices do not tri-state correctly in SPI mode, see this tutorial:

https://www.dorkbotpdx.org/blog/paul/better_spi_bus_design_in_3_steps

Greetings,

If I use the code you suggested, the DAC outputs stay at 0 V. The DACs don't work at all. I've tested the code. If I call DAC_handle manually the DACs work, but the voltages are 0.9 V and 2.3 V (and not 0.5 and 2.7 V), I'm using a 10k resistor in series with the DAC output.

Regards,
Daniel

Once enabled, DAC output range is between 1/6 * 3.3V and 5/6 * 3.3 V, therefore you can't observe 0 V.

However, in the datasheet page 1359, I read:

Warning: Using this mode (MAXS) , the EOC interrupt of the DACC_IER register should not be used as it is 2 DACC Clock periods late.

So I guess you should replace
DACC->DACC_IER |= DACC_IER_EOC; // Don't use MAXS mode

by

DACC->DACC_IER |= DACC_IER_TXRDY; // with MAXS mode

I think it was not 0 V that I was observing, it seemed more like high impedance connected to ground. Like if the output of the DAC was disabled.

ard_newbie:
So I guess you should replace

DACC->DACC_IER |= DACC_IER_TXRDY; // with MAXS mode

I've replaced the interrupt flag to TX Ready and now I have full voltage range on both DACS (0.54 V to 2.71 V), the test sketch is finally working.

The problem now is, I can't put my code to read a wav file from SD to the DAC working, because it gets stuck, due to the DACC interrupts, I don't know what to do. Could you help me, please!

Regards,
Daniel

DumpAudioFile5_optimized_dma.ino (23.4 KB)

I don't have a Due but I do wonder about some things,

Does the Due have an SD or SDFat library like the AVR-duinos? It uses a buffer to write to SD in burst mode.

AVR SPI port clocks a word out in 8 SPI clock cycles, default is cpu/4, giving the chip 32 cycles to be ready for the next load.

ARM has a 32-bit word. Is that what you get out every 2 or 3 micros? It's 4 bytes, not 1.
Does it have an SPI clock divider? I'm sure it must, how else to master slower devices?

I will analyse later your code, but I can already see weird things:

Lines 265 and 266:

Timer Counter 0 Channel 2 triggers DAC conversions (you are no more in free running mode !) thru TIOA2 internally . However, you set TC_CPRD to timerperiod = 0 ? and TC_CDTY to timerperiod (?) = 0 ?  CDTY range should be: 0 < CDTY < CPRD and obviously timerperdiod not equal to 0.

Since you are not using Free Running mode, to debug, I would first try to read the SD card then output the result at a relatively slow frequency (e.g. 44.1 KHz) far from 1 MHz and see what happens with higher frequencies.

GoForSmoke is right, there are faster SD libraries for DUE (SDFAT) or (more tricky) you can use High Speed Multimedia Interface peripheral (HSMCI). It has been rarely used for some reason, but IMO this is super fast reading/Writing to an SD card.

Edit: TurboSpi is by far the best way to go to speed up SPI (42 MHz)

One solution to keeping the SD selected is to use a serial port as dedicated SPI bus as mentioned above but still write whole blocks at a time. The SD card has a controller with a wear map of the flash and it can take a little extra time addressing a block so extra buffer space for incoming data is a good idea.

Nick Gammon has serial port as master mode SPI and slave mode SPI for AVR's on his blog pages, maybe by now he's done ARM as well.

Greetings

ard_newbie:
I will analyse later your code, but I can already see weird things:

Lines 265 and 266:

Timer Counter 0 Channel 2 triggers DAC conversions (you are no more in free running mode !) thru TIOA2 internally . However, you set TC_CPRD to timerperiod = 0 ? and TC_CDTY to timerperiod (?) = 0 ?  CDTY range should be: 0 < CDTY < CPRD and obviously timerperdiod not equal to 0.

TC_CPRD and TC_CDTY are not 0 they are defined in the setup, taking in account the sample rate of the wav file, before tc_setup() is called, anyway I've changed the default value.

It seems like enabling NVIC interrupts for DACC are disabling all the other interrupts like the SPI (SD) and the USART (Serial). Is it normal?

GoForSmoke:
One solution to keeping the SD selected is to use a serial port as dedicated SPI bus as mentioned above but still write whole blocks at a time. The SD card has a controller with a wear map of the flash and it can take a little extra time addressing a block so extra buffer space for incoming data is a good idea.

Nick Gammon has serial port as master mode SPI and slave mode SPI for AVR's on his blog pages, maybe by now he's done ARM as well.

I don't understand, which serial port are you refering? The USART?

GoForSmoke:
I don't have a Due but I do wonder about some things,

Does the Due have an SD or SDFat library like the AVR-duinos? It uses a buffer to write to SD in burst mode.

The Due has SD and SDFat libraries like the other AVR based arduinos.

ard_newbie:
GoForSmoke is right, there are faster SD libraries for DUE (SDFAT) or (more tricky) you can use High Speed Multimedia Interface peripheral (HSMCI). It has been rarely used for some reason, but IMO this is super fast reading/Writing to an SD card.

I have to check if the reading and writing functions are similar, otherwise, I'll have to rewrite the reading code. Improving the SD reading speed could be helpful.

ard_newbie:
Since you are not using Free Running mode, to debug, I would first try to read the SD card then output the result at a relatively slow frequency (e.g. 44.1 KHz) far from 1 MHz and see what happens with higher frequencies.

I need to read the whole wav file and not a few samples, it's very hard to tell if the signal is ok, with 100 or 200 ms of samples (I can't save more time in the Due's RAM). I need to be reading the file from the SD and feed samples to DAC.

ard_newbie:
Edit: TurboSpi is by far the best way to go to speed up SPI (42 MHz)

I don't need to go that fast, I think somewhere between 10 MHz and 20 MHz is ok.

Anyway, my goal is to make something similar to the Audio library, that can read wav files, but with a recording feature added.

The code should:

  • Read an wav file sampled at up to 44.1 kHz or 48 kHz and feed samples to the DAC (preferrably external) because a resolution of at least 16 bits is desirable. Both DAC and SD card communicate thru SPI
  • Record an wav file to the SD sampled using the same frequency, 44.1 kHz or 48 kHz, with samples from an ADC (also SPI and external with at least 16 bit resolution)

The circuit is supposed to be used with ECG frontends to perform closed loop tests. For example introducing some noises to the ECG signal on a wav file to preform noise rejection tests to ECG frontends. The result is saved on the SD card using the wav format.

The circuit is made of:

|->>>>>>>>>>>>>>>>>>>>-|
SD Card <->Arduino Due -> DAC -> signal conditioning circuits -> ECG frontend -> ADC
|-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-|

For now I'm trying only to make the first step, reading a wav file sampled at 44.1 kHz or 48 kHz, but it's very difficult.

Regards,
Daniel

DumpAudioFile5_optimized_dma.ino (23.5 KB)