1
votes

I've set up two STM32 Boards, one as SPI-master, the other one as slave. I write directly to registers without any framework. Master to slave communication is working perfectly. But the slave sends garbage sometimes.

I first tried interrupts, but the slave would always send garbage and often receive garbage. Now I implemented DMA. This is working way better, the slave now always receives correct data. But sending is still an issue.

If the transmission is 3 to 5 Bytes long the data from the slave is correct in 95% of all cases. If the transmission is longer then 5 bytes, then after the 4th or 5th byte there is just random byte foo. But the first 4 bytes are nearly (95%) always correct.

The signals are clean, I checked them with an oscilloscope. The data which the master receives shows up properly on MISO. So I guess the slave somehow writes garbage into the SPI DR, or the data register gets messed up. I know SPI slaves on non-FPGAs are tricky, but this really is unexpected...

Anyone can point me a direction? I'm desperate and thankful for any bit of advice.

This is the code

void DMA1_Stream3_IRQHandler( void )
{
    if (spi2_slave)
    {
        while( (spi_spc->SR & (1<<1)) == 0 );   // must wait for TXE to be set!
        while( spi_spc->SR & (1<<7) );  // must wait for busy to clear!

        DMA1_Stream3->CR &= ~(1<<0);                // Disable stream 3
        while((DMA1_Stream3->CR & (1<<0)) != 0);    // Wait till disabled

        DMA1_Stream3->NDTR = 3; // Datenmenge zum Empfangen
        DMA1_Stream3->CR |= (1<<0); // Enable DMA1_Stream3 (TX)

        DMA1->LIFCR = (1<<27);      // clear Transfer complete in Stream 3

        // fire SPI2 finished CBF
        if (spi2_xfer_done != 0)
        {
            if (spi2_xfer_len > 0)
            {
                spi2_xfer_done(spi2_rx_buffer, spi2_xfer_len);
            }
        }

    }
    else
    {
        while( spi_spc->SR & (1<<7) );  // must wait for busy to clear!

        GPIOB->ODR |= (1<<12);              // Pull up SS Pin

        spi_spc->CR2 &= ~((1<<0) | (1<<1)); // Disable TX and RX DMA request lines
        spi_spc->CR1 &= ~(1<<6);            // 6:disableSPI

        DMA1->LIFCR = (1<<27);  // clear Transfer complete in Stream 3

        // fire SPI2 finished CBF
        if (spi2_xfer_done != 0)
        {
            spi2_xfer_done(spi2_rx_buffer, spi2_xfer_len);
        }
        while( (spi_spc->SR & (1<<1)) == 0 );   // must wait for TXE to be set!

    }

}

// For Slave TX DMA
void DMA1_Stream4_IRQHandler( void )
{
    DMA1_Stream4->CR &= ~(1<<0);                // Disable stream 4
    while((DMA1_Stream4->CR & (1<<0)) != 0);    // Wait till disabled

    spi_spc->CR2 &= ~(1<<1);    // Disable TX DMA request lines
    DMA1->HIFCR = (1<<5);       // clear Transfer complete in Stream 4

}

void mcu_spi_spc_init_slave(void (*xfer_done)(uint8_t* data, uint32_t dlen))
{
    spi2_slave = 1;
    spi2_xfer_done = xfer_done;

    for (int c=0;c<SPI2_BUFFER_SIZE;c++)
    {
        spi2_tx_buffer[c] = 'X';
        spi2_rx_buffer[c] = 0;
    }

    // Enable the SPI2 peripheral clock
    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;

    // Enable port B Clock
    RCC->AHB1ENR |= (1<<1);

    // Enable DMA1 Clock
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;

    // Reset the SPI2 peripheral to initial state
    RCC->APB1RSTR |=  RCC_APB1RSTR_SPI2RST;
    RCC->APB1RSTR &= ~RCC_APB1RSTR_SPI2RST;

    /*
 * SPC SPI2 SS:     Pin33 PB12
 * SPC SPI2 SCK:    Pin34 PB13
 * SPC SPI2 MISO:   Pin35 PB14
 * SPC SPI2 MOSI:   Pin36 PB15
     */

    // Configure the SPI2 GPIO pins
    GPIOB->MODER |= (2<<24) | (2<<26) | (2<<28) | (2<<30);
    GPIOB->PUPDR |= (02<<26) | (2<<28) | (2<<30);
    GPIOB->OSPEEDR |= (3<<24) | (3<<26) | (3<<28) | (3<<30);        // "very High speed"
    GPIOB->AFR[1] |= (5<<16) | (5<<20) | (5<<24) | (5<<28);     // Alternate function 5 (SPI2)

    //-------------------------------------------------------

    // Clock Phase and Polarity = 0
    // CR1 = LSByte to MSByte, MSBit first
    // DFF = 8bit
    // 6 MHz Clock (48MHz / 8)
    spi_spc->CR1 = (7<<3) | (0<<2) | (0<<1) | (1<<0)    // 0:CPHA, 1:CPOL, 2:MASTER, 3:CLOCK_DIVIDER
                | (0<<7) | (0<<11);                     // 7:LSB first, 11:DFF(8Bit)

    spi_spc->CR2 = (0<<2) | (1<<1) | (1<<0);            // 2:SSOE, 0:Enable RX DMA IRQ, 1:Enable TX DMA IRQ

    // DMA config   (Stream3:RX p2mem, Stream4:TX mem2p
    // DMA for RX Stream 3 Channel 0
    DMA1_Stream3->CR &= ~(1<<0);                // EN = 0: disable and reset
    while((DMA1_Stream3->CR & (1<<0)) != 0);    // Wait

    DMA1_Stream4->CR &= ~(1<<0);                // EN = 0: disable and reset
    while((DMA1_Stream4->CR & (1<<0)) != 0);    // Wait

    DMA1->LIFCR = (0x3D<<22);   // clear all ISRs related to Stream 3
    DMA1->HIFCR = (0x3D<< 0);   // clear all ISRs related to Stream 4

    DMA1_Stream3->PAR = (uint32_t) (&(spi_spc->DR));    // Peripheral addresse
    DMA1_Stream3->M0AR = (uint32_t) spi2_rx_buffer;     // Memory addresse

    DMA1_Stream3->NDTR = 3; // Datenmenge zum Empfangen

    DMA1_Stream3->FCR &= ~(1<<2);   // ENABLE Direct mode by CLEARING Bit 2
    DMA1_Stream3->CR = (0<<25) |    // 25:Channel selection(0)
                       (1<<10) |    // 10:increment mem_ptr,
                       (0<<9) |     // 9: Do not increment periph ptr
                       (0<<6) |     // 6: Dir(P -> Mem)
                       (1<<4);      // 4: finish ISR

    // DMA for TX Stream 4 Channel 0
    DMA1_Stream4->PAR = (uint32_t) (&(spi_spc->DR));    // Peripheral addresse
    DMA1_Stream4->M0AR = (uint32_t) spi2_tx_buffer;     // Memory addresse

    DMA1_Stream4->NDTR = 1; // Datenmenge zum Senden (dummy)

    DMA1_Stream4->FCR &= ~(1<<2);   // ENABLE Direct mode by CLEARING Bit 2
    DMA1_Stream4->CR = (0<<25) |    // 25:Channel selection(0)
                       (1<<10) |    // 10:increment mem_ptr,
                       (0<<9) |     // 9: Do not increment periph ptr
                       (1<<6) |     // 6: Dir(Mem -> P)
                       (1<<4);

    // Setup the NVIC to enable interrupts.
    // Use 4 bits for 'priority' and 0 bits for 'subpriority'.
    NVIC_SetPriorityGrouping( 0 );

    uint32_t pri_encoding = NVIC_EncodePriority( 0, 1, 0 );
    NVIC_SetPriority( DMA1_Stream4_IRQn, pri_encoding );
    NVIC_EnableIRQ( DMA1_Stream4_IRQn );
    NVIC_SetPriority( DMA1_Stream3_IRQn, pri_encoding );
    NVIC_EnableIRQ( DMA1_Stream3_IRQn );

    DMA1_Stream3->CR |= (1<<1); // Enable DMA1_Stream3 (RX)
    spi_spc->CR1 |= (1<<6);     // 6:EnableSPI

}

In the future the system has to send and receive roughly 500 bytes.

1
My first instinct would be to check the clock polarity and phase is set correctly at both ends, but you have only posted one half of the code. Incidentally, there are named constants in the device header file for all those (1 << X). It is very easy to make a mistake when all your code is just a few hundred lines of meaningless magic numbers.Tom V
Another test I would do is turn the clock right down and see if it makes a difference. If it doesn't then this rules out both a signal integrity problem and DMA underflow/overflow.Tom V
So, Stream3 is RX on the slave, and Stream4 is TX from the slave? It looks like your Stream4 ISR is incomplete. You disable the channel and clear the flags, but don't reset it. You've also only got NDTR set to 1. (Out of curiosity, you say it's usually okay for 3-5 bytes, and Stream3->NDTR is 3. What happens if you change that value? Probably just coincidence, but have a play.)Ashley Miller
Also, I'm no expert with DMA, but do you really need to disable, clear, and re-enable the DMA to reset the memory pointer and counter? Is there no DMA mode that automatically cycles over a fixed region?Ashley Miller

1 Answers

0
votes

So, I did it. It was a whole bunch of things. Also, my assumption in the question was wrong. My slave did not receive/send valid data.

  1. The signals were shown as clean by the oscilloscope, but the scope itself was adding noise to the lines, that was not visible on the scope itself. Not measuring the lines helped.

  2. I put 100 OHM resistors close to the MASTER pins. This was not working, out of desperation I put the resistors close to the slave instead. Suddenly I got valid data. (This has been the main culprit all along)

  3. According to the comment of Ashley Miller, I implemented a circular buffer, where I always send a fixed length every time. So the slave knows exactly what to expect. This mitigated eventual errors that could be produced when switching off / resetting the DMA shortly after the transmission.

  4. The UART tricked me also. When getting too much data at once ( as little as 20 or 30 bytes! ) my terminal program gliched and threw the bytes randomly around. So part of the problem was just that... I'm using GtkTerm for those who are interested.

  5. The Clock mode CPOL= 0 and CPH = 0 doesn't work at all. I set both master and slave to the same setting and it just received garbage. If I loop back the master to itself (connect MISO to MOSI a.k.a. exclude the slave) then it works regardless of clock mode. This seems to stem from a timing issue, where the slave has to react too fast and can't handle even the slowest possible speed (approx. 100 kHz). I did not go into details on this.

I hope I could help someone with this.