STM32F405 裸机 spi 从机 - MISO 数据有时会搞砸

STM32F405 bare metal spi slave - MISO data messed up sometimes

我设置了两块STM32 Board,一块作为SPI-master,另一块作为slave。 我直接写寄存器,没有任何框架。 主从通信工作正常。但是slave有时会发送垃圾。

我第一次尝试中断,但是slave总是发送垃圾并且经常接收垃圾。 现在我实施了 DMA。这是更好的工作方式,奴隶现在总是收到正确的数据。但是发送还是个问题

如果传输长度为 3 到 5 个字节,则来自从站的数据在 95% 的情况下都是正确的。 如果传输超过 5 个字节,那么在第 4 个或第 5 个字节之后只有随机字节 foo。但是前 4 个字节几乎 (95%) 总是正确的。

信号很干净,我用示波器检查过。主机收到的数据正确显示在 MISO 上。所以我猜从机以某种方式将垃圾写入 SPI DR,或者数据寄存器被弄乱了。 我知道非 FPGA 上的 SPI 从设备很棘手,但这确实出乎意料...

谁能给我指路?我很绝望,也很感谢你的任何建议。

这是代码

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

}

以后系统要发送和接收大约 500 个字节。

所以,我做到了。这是一大堆事情。另外,我在问题中的假设是错误的。我的奴隶没有 receive/send 有效数据。

  1. 信号被示波器显示为干净,但示波器本身给线路增加了噪声,这在示波器本身上是看不到的。不测量线条有帮助。

  2. 我将 100 欧姆电阻放在靠近主引脚的位置。这是行不通的,出于绝望,我把电阻器放在靠近从机的地方。突然我得到了有效数据。 (这一直是罪魁祸首)

  3. 根据Ashley Miller的评论,我实现了一个循环缓冲区,每次发送固定长度。所以奴隶确切地知道会发生什么。这减轻了在传输后不久关闭/重置 DMA 时可能产生的最终错误。

  4. UART 也骗了我。当一次获取太多数据(少至 20 或 30 个字节!)时,我的终端程序出现故障并随机丢弃字节。所以部分问题在于......我正在为那些感兴趣的人使用 GtkTerm。

  5. 时钟模式CPOL=0和CPH=0根本不起作用。我将 master 和 slave 设置为相同的设置,它只是收到垃圾。如果我将主机环回自身(将 MISO 连接到 MOSI a.k.a。排除从机)那么无论时钟模式如何,它都可以工作。 这似乎源于一个时序问题,从站必须反应太快,甚至无法处理最慢的速度(约 100 kHz)。这个我就不细说了。

我希望我能帮助别人。