STM32F405裸金属spi从机-MISO数据有时会出错



我已经设置了两个STM32板,一个作为SPI主板,另一个作为从板。我直接写寄存器,没有任何框架。主从通信运行良好。但奴隶有时会发送垃圾。

我第一次尝试中断,但从属总是发送垃圾,并且经常接收垃圾。现在我实现了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. 示波器显示信号是干净的,但示波器本身给线路添加了噪声,而这在示波器本身上是看不到的。不测量线条有帮助。

  2. 我把100欧姆电阻靠近主引脚。这不起作用,出于绝望,我把电阻放在了奴隶身上。突然间,我得到了有效的数据。(这一直是罪魁祸首(

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

  4. UART也欺骗了我。当一次获得太多数据(只有20或30个字节!(时,我的终端程序会出错,并随机丢弃字节。所以问题的一部分只是。。。我正在为那些感兴趣的人使用GtkTerm。

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

我希望我能帮助别人。

最新更新