SPI+DMA stops running after a number of cycles

Hello experts,

I have to develop a comunication via SPI + DMA from RP2050 to a FRAM (MB85RS256B).

I'm able to write and read individual values to/from the FRAM without the DMA. I'm using Arduino IDE and I do not have a debbuger.

Well, I started to use the example pico-examples/spi/spi_dma/spi_dma.c at master · raspberrypi/pico-examples · GitHub with some changes. I chaged a little the configuration (SPI clock, SPI pins, and setting a 3-wire SPI (controlling CS using the GPIO)). I keept all the DMA configuration from the example except that I removed the loopback mode.

My application will need to read a big chunk of data but for now I'm doing tests with 2048 samples. In order to read data from FRAM: I adapted txbuf to incorporate the op-code (READ), start address and the dummy data (0xFF) that needs to be written in order to generate the clocks. Also, txbuf is in a one-byte format (uint8_t).

The FRAM is shared with a MSPM0 that writes on it. So I need a semaphore to control the use of the bus. MSP changes GP0 indicating that the bus is free to use, it happens every 100 ms and it triggers an interrupton (eventInt) on RP2050.

My application (attached), for debug purpose, prints the read values from every memory position and increments this position for the next print.

The problem is, using TEST_SIZE of 2048, it works fine until it iterates 253 times, after that the SPI communication stops (nothing on oscilloscope). Interestingly, the interruption still been trigged every 100 ms but it looks like the program is not passing at loop1() anymore, since the LED stays on after the crash. This code should run infinitely and not stop after 253 iterations!!!

I changed the SPI clock frequency for 500kHz, 2MHz and 20 MHz, in all cases after 253 iteration, it crashes.

I changed my TEST_SIZE to 1024 and SPI clock frequency for 500kHz, 2MHz and 20 MHz, in all cases after 508 iteration, it crashes.

Please, could someone give me any advice or suggestion to fix my code?

Thanks,
Andrea

// link https://github.com/raspberrypi/pico-examples/blob/master/spi/spi_dma/spi_dma.c

#include <SPI.h>
#include <SPISlave.h>
#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/spi.h"
#include "hardware/dma.h"

#define TEST_SIZE 2048
//SPI configurations
#define PIN_SCK  2
#define PIN_MOSI 3
#define PIN_MISO 4
#define PIN_CS   5
#define SPI_PORT spi0

// Grab some unused dma channels
const uint dma_tx = dma_claim_unused_channel(true);
const uint dma_rx = dma_claim_unused_channel(true);

const uint8_t INT_PIN = 1;              // Signal interrupt pin, signal from MSP (PA9)
const uint8_t BUS_FREE = 0;             // FRAM bus free, signal to MSP (PA14)
const uint8_t LED = 25;                 // Built-in LED on GP25
volatile bool fram_available = false;   // Indicates if FRAM is available for reading
volatile uint8_t txbuf[TEST_SIZE];  
volatile uint8_t rxbuf[TEST_SIZE];
const uint8_t offset = 3;  
uint16_t idx = offset;

void eventInt() {
  fram_available = true; // set the bool for the loop()
  Serial.println("eventInt...");
}

// Core 0 will set GPIO
void setup() {

pinMode(INT_PIN, INPUT); 
pinMode(LED, OUTPUT);
pinMode(BUS_FREE, OUTPUT);

gpio_put(BUS_FREE, HIGH); // prepare to signal to MSP
delay(5000);
gpio_put(BUS_FREE, LOW); // MSP starts the write process
delay(1000); // wait for MSP to finish any write process

attachInterrupt(digitalPinToInterrupt(INT_PIN), eventInt, RISING);
Serial.println("setup done!");

}

// Core 1 will set SPI
void setup1() {

delay(5000);

// Initialize SPI channel (channel, baud rate set to 20MHz)
spi_init(SPI_PORT, 20000000);

// Format SPI channel (channel, data bits per transfer, polarity, phase, order)
spi_set_format(SPI_PORT, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);

// Map SPI signals to GPIO ports, acts like framed SPI without CS mapping
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
//gpio_set_function(PIN_CS, GPIO_FUNC_SPI) ;
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);

// CS mapping
gpio_init(PIN_CS);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_put(PIN_CS, HIGH);

// dummy array
for (uint i = offset; i < TEST_SIZE; ++i) {
  txbuf[i] = 0xFF; 
}

// We set the outbound DMA to transfer from a memory buffer to the SPI transmit FIFO paced by the SPI TX FIFO DREQ
// The default is for the read address to increment every element (in this case 1 byte = DMA_SIZE_8)
// and for the write address to remain unchanged.
txbuf[0] = 0x03; // op-code READ
txbuf[1] = 0x00; // start address MSB
txbuf[2] = 0x00; // start address LSB
dma_channel_config c = dma_channel_get_default_config(dma_tx);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_dreq(&c, spi_get_dreq(spi0, true));
dma_channel_configure(dma_tx, &c,
                      &spi_get_hw(spi0)->dr, // write address
                      txbuf, // read address
                      TEST_SIZE, // element count (each element is of size transfer_data_size)
                      false); // don't start yet

// We set the inbound DMA to transfer from the SPI receive FIFO to a memory buffer paced by the SPI RX FIFO DREQ
// We configure the read address to remain unchanged for each element, but the write
// address to increment (so data is written throughout the buffer)
c = dma_channel_get_default_config(dma_rx);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_dreq(&c, spi_get_dreq(spi0, false));
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
dma_channel_configure(dma_rx, &c,
                      rxbuf, // write address
                      &spi_get_hw(spi0)->dr, // read address
                      TEST_SIZE, // element count (each element is of size transfer_data_size)
                      false); // don't start yet

Serial.println("setup1 done!");

}

void loop() {

  if (fram_available) {
    fram_available = false; // Reset the flag

    gpio_put(LED, HIGH);  // LED on
    gpio_put(BUS_FREE, HIGH); // prepare to signal MSP
    gpio_put(PIN_CS, LOW);
    
    // start them exactly simultaneously to avoid races (in extreme cases the FIFO could overflow)
    dma_start_channel_mask((1u << dma_tx) | (1u << dma_rx));
    dma_channel_wait_for_finish_blocking(dma_rx);
    if (dma_channel_is_busy(dma_tx)) {
        Serial.println("not good...");
        panic("RX completed before TX");
    }

    gpio_put(BUS_FREE, LOW); // signals to MSP that it can use the bus
    gpio_put(PIN_CS, HIGH);

    //uint16_t value = (rxbuf[idx+1] << 8) | rxbuf[idx];
    uint16_t value = ((rxbuf[(2 * idx) - offset] << 8) | rxbuf[(2 * idx) - offset + 1]);
    Serial.printf("Index: %d, value: %u\n", idx, value);
    idx += 1;
    if(idx == (TEST_SIZE/2)){
        idx = offset;
    }

  }

  __wfi();  // Wait for interrupt

}

void loop1() {

  delay(50); 
  gpio_put(LED, LOW); // LED off

}

What is an "MSPM0"?

Hey gfvalvo,
The MSPM0 is Arm Cortex-M0+ MCUs.(Arm Cortex-M0+ MCUs | TI.com).
Sorry, my bad, I thought it was pretty obvious.
Regards,
Andrea