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 有效数据。
信号被示波器显示为干净,但示波器本身给线路增加了噪声,这在示波器本身上是看不到的。不测量线条有帮助。
我将 100 欧姆电阻放在靠近主引脚的位置。这是行不通的,出于绝望,我把电阻器放在靠近从机的地方。突然我得到了有效数据。 (这一直是罪魁祸首)
根据Ashley Miller的评论,我实现了一个循环缓冲区,每次发送固定长度。所以奴隶确切地知道会发生什么。这减轻了在传输后不久关闭/重置 DMA 时可能产生的最终错误。
UART 也骗了我。当一次获取太多数据(少至 20 或 30 个字节!)时,我的终端程序出现故障并随机丢弃字节。所以部分问题在于......我正在为那些感兴趣的人使用 GtkTerm。
时钟模式CPOL=0和CPH=0根本不起作用。我将 master 和 slave 设置为相同的设置,它只是收到垃圾。如果我将主机环回自身(将 MISO 连接到 MOSI a.k.a。排除从机)那么无论时钟模式如何,它都可以工作。
这似乎源于一个时序问题,从站必须反应太快,甚至无法处理最慢的速度(约 100 kHz)。这个我就不细说了。
我希望我能帮助别人。
我设置了两块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 有效数据。
信号被示波器显示为干净,但示波器本身给线路增加了噪声,这在示波器本身上是看不到的。不测量线条有帮助。
我将 100 欧姆电阻放在靠近主引脚的位置。这是行不通的,出于绝望,我把电阻器放在靠近从机的地方。突然我得到了有效数据。 (这一直是罪魁祸首)
根据Ashley Miller的评论,我实现了一个循环缓冲区,每次发送固定长度。所以奴隶确切地知道会发生什么。这减轻了在传输后不久关闭/重置 DMA 时可能产生的最终错误。
UART 也骗了我。当一次获取太多数据(少至 20 或 30 个字节!)时,我的终端程序出现故障并随机丢弃字节。所以部分问题在于......我正在为那些感兴趣的人使用 GtkTerm。
时钟模式CPOL=0和CPH=0根本不起作用。我将 master 和 slave 设置为相同的设置,它只是收到垃圾。如果我将主机环回自身(将 MISO 连接到 MOSI a.k.a。排除从机)那么无论时钟模式如何,它都可以工作。 这似乎源于一个时序问题,从站必须反应太快,甚至无法处理最慢的速度(约 100 kHz)。这个我就不细说了。
我希望我能帮助别人。