STM32F405 裸机 SPI 从机 - MISO 数据有时会混乱

Posted

技术标签:

【中文标题】STM32F405 裸机 SPI 从机 - MISO 数据有时会混乱【英文标题】:STM32F405 bare metal spi slave - MISO data messed up sometimes 【发布时间】:2021-06-15 03:25:48 【问题描述】:

我已经设置了两块STM32板,一个作为SPI-master,另一个作为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 个字节。

【问题讨论】:

我的第一反应是检查两端的时钟极性和相位设置是否正确,但您只发布了一半的代码。顺便说一句,设备头文件中有所有这些(1 我要做的另一项测试是将时钟调低,看看它是否有所作为。如果不是,则排除信号完整性问题和 DMA 下溢/上溢。 那么,Stream3 是从站的 RX,Stream4 是从站的 TX?看起来您的 Stream4 ISR 不完整。您禁用通道并清除标志,但不要重置它。您也只有将NDTR 设置为1。 (出于好奇,您说 3-5 个字节通常是可以的,而 Stream3-&gt;NDTR3。如果您更改该值会发生什么?可能只是巧合,但玩一玩。) 另外,我不是 DMA 专家,但您真的需要禁用、清除和重新启用 DMA 来重置内存指针和计数器吗?没有 DMA 模式可以自动在固定区域上循环吗? 【参考方案1】:

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

    示波器显示信号干净,但示波器本身给线路添加了噪声,这在示波器本身上是不可见的。不测量线条有帮助。

    我在 MASTER 引脚附近放置了 100 个欧姆电阻。这不起作用,出于绝望,我将电阻器放在靠近从机的地方。突然我得到了有效的数据。 (这一直是罪魁祸首)

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

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

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

我希望我能帮助别人。

【讨论】:

以上是关于STM32F405 裸机 SPI 从机 - MISO 数据有时会混乱的主要内容,如果未能解决你的问题,请参考以下文章

STM32F1 - 在裸机上使用主 SPI

STM32F4在半双工/单工模式下停用SPI的正确方法

SPI 串行Flash闪存W25Q128FV 的使用(STM32F407)_软件篇

stm32f405和stm32f407的区别

带有 STM32F7 的 SPI 从模式和循环 DMA

STM32F0 在 SPI 接收中断时从 STOP 退出