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->NDTR
是 3
。如果您更改该值会发生什么?可能只是巧合,但玩一玩。)
另外,我不是 DMA 专家,但您真的需要禁用、清除和重新启用 DMA 来重置内存指针和计数器吗?没有 DMA 模式可以自动在固定区域上循环吗?
【参考方案1】:
所以,我做到了。这是一大堆事情。另外,我在问题中的假设是错误的。我的奴隶没有接收/发送有效数据。
示波器显示信号干净,但示波器本身给线路添加了噪声,这在示波器本身上是不可见的。不测量线条有帮助。
我在 MASTER 引脚附近放置了 100 个欧姆电阻。这不起作用,出于绝望,我将电阻器放在靠近从机的地方。突然我得到了有效的数据。 (这一直是罪魁祸首)
根据 Ashley Miller 的评论,我实现了一个循环缓冲区,我每次总是发送一个固定长度的缓冲区。所以奴隶确切地知道会发生什么。这减轻了在传输后不久关闭/重置 DMA 时可能产生的最终错误。
UART 也欺骗了我。当一次获取太多数据时(少至 20 或 30 个字节!),我的终端程序出现故障并随机抛出字节。所以问题的一部分就是......我正在为那些感兴趣的人使用 GtkTerm。
时钟模式 CPOL= 0 和 CPH = 0 根本不起作用。我将主机和从机都设置为相同的设置,它只是收到垃圾。如果我将主设备循环回自身(将 MISO 连接到 MOSI,即排除从设备),那么无论时钟模式如何,它都可以工作。 这似乎源于时间问题,从机必须反应太快,甚至无法处理最慢的速度(大约 100 kHz)。我没有详细说明这一点。
我希望我能帮助别人。
【讨论】:
以上是关于STM32F405 裸机 SPI 从机 - MISO 数据有时会混乱的主要内容,如果未能解决你的问题,请参考以下文章