关于STM32H7 SPI在slave模式下的限制
About the limitation of STM32H7 SPI in slave mode
我正在两个 Nucleo STM32H743 板之间实现 SPI 通信。
我已将 SPI 配置为全双工模式,启用 CRC,以最大 SPI 频率(SCK 频率为 100MHz)。
只要我只从主端传输和从从端接收,它就工作正常。
这是我用来从主机发送和从从机接收的代码。请注意,我知道这是快速而肮脏的代码,它只是一个概念证明,所以请不要评论编码风格(我知道,硬编码值,寄存器指针等......)。作为记录,我还使用了具有相同行为的 STMicro HAL 函数 HAL_SPI_Transmit
和 HAL_SPI_Receive
。
volatile unsigned long *CR1 = (unsigned long *)0x40013000;
volatile unsigned long *CR2 = (unsigned long *)0x40013004;
volatile unsigned long *TXDR = (unsigned long *)0x40013020;
volatile unsigned long *RXDR = (unsigned long *)0x40013030;
volatile unsigned long *SR = (unsigned long *)0x40013014;
volatile unsigned long *IFCR = (unsigned long *)0x40013018;
volatile unsigned long *TXCRC = (unsigned long *)0x40013044;
volatile unsigned long *RXCRC = (unsigned long *)0x40013048;
volatile unsigned long *CFG2 = (unsigned long *)0x4001300C;
void HAL_SPI_TransmitRegister(uint32_t Data, uint32_t Dummy)
{
// size of transfer (TSIZE)
*CR2 = 2;
// Enable SPI peripheral
*CR1 |= 1;
// Master transfer start
*CR1 |= SPI_CR1_CSTART;
// Fill the FIFO
*TXDR = Data;
*TXDR = Dummy;
while ( ((*SR) & SPI_FLAG_EOT) == 0 );
// clear flags
*IFCR = 0xFFFFFFFF;
// disable SPI
*CR1 &= ~1;
}
void HAL_SPI_ReceiveRegister(uint32_t *pData)
{
// two 32 bits words to be received
*CR2 = 2;
// Enable SPI peripheral
*CR1 |= 1;
while ( ((*SR) & (SPI_FLAG_EOT | SPI_FLAG_RXWNE)) == 0 );
*(pData) = (*RXDR);
while ( ((*SR) & (SPI_FLAG_EOT | SPI_FLAG_RXWNE)) == 0 );
*(pData+1) = (*RXDR);
// clear flags
*IFCR = 0xFFFFFFFF;
// disable SPI
*CR1 &= ~1;
}
所以上面的代码工作正常。现在这就是它开始变得有趣的地方:我想从奴隶那里传输一些东西。我修改了代码如下(也尝试了具有相同行为的函数 HAL_SPI_TransmitReceive
):
void HAL_SPI_ReceiveRegister(uint32_t *pData)
{
unsigned long response = 0xABCDEF99;
*CR2 = 2;
/* Enable SPI peripheral */
*CR1 |= 1;
// write response in advance
*TXDR = 0;
*TXDR = response;
// first 4 bytes are the command
while ( ((*SR) & (SPI_FLAG_EOT | SPI_FLAG_RXWNE)) == 0 );
*(pData) = (*RXDR);
// next 4 bytes are the dummy bytes used to send response
while ( ((*SR) & (SPI_FLAG_EOT | SPI_FLAG_RXWNE)) == 0 );
*(pData+1) = (*RXDR);
// clear flags
*IFCR = 0xFFFFFFFF;
// disable SPI
*CR1 &= ~1;
}
Master 收到乱码,但 Slave 收到正确数据。
现在STM32H7x3的datasheet确实提到了SPI在Slave模式下的限制,但我不明白"slave mode transmitter"是什么意思:
我的问题是:
数据表 "slave mode transmitter" 是什么意思?
我不明白为什么slave能够以100Mhz接收但不能发送,因为通信是全双工的。
如果数据仅单向流动,SPI 不必是全双工的。
当只有master在给slave发数据,slave没有数据要发时,软件不会在发送数据寄存器里写任何东西,还有东西 发送出去,因为根据定义,当 master 向时钟线发出脉冲时,输出位是 MISO 线上的任何内容。 SPI 从机不能像 I2C 从机那样延长时钟,数据 必须 在时钟脉冲到达时准备好发送。当发送寄存器为空时,SPI 控制器可能会发送全 0、全 1、重复最后一个字节或只是垃圾。
STM32H7 SPI 控制器可以配置为执行上述任何操作(以及更多),或者它可以配置为 只接收从机,MASTER=0
和 SPIx->CFG2
中的 COMM[1:0]=10
,则它不会在 MISO 上输出任何内容,除非将其重新分配给其他外围功能,否则该引脚将悬空。
相反,如果从站不关心主站在时钟脉冲时发送什么,它可以配置为仅传输从站,忽略 MOSI。在这种情况下,当没有人读取接收数据寄存器时,控制器既不会产生接收中断,也不会产生溢出错误。
作为SPI slave比master端更复杂
因为从站有两个时钟要遵守。来自控制发送和接收移位器的主机的外部时钟,以及用于访问寄存器的内部总线时钟。确保跨这些 时钟域 的数据完整性,即没有中途写入的数据从一个内部寄存器传输到另一个内部寄存器并非微不足道。 这就是为什么主从模式有不同的限制。
发送和接收移位器不一样。
他们有不同的任务要做,一个将数据移出,另一个将数据移入。可以实现一个同时对两个操作使用同一组触发器的方法,但它可能会变得太复杂上面的双时钟要求,所以有两个。 ST 的工程师发现,其中一个移位器在 29-31 MHz 以上无法可靠工作。只有他们才能判断这是一个无意的限制,还是这样做更简单(即更便宜),而且他们中没有大客户真正想要(即愿意为此多支付 0.0003 美元)此功能。
我正在两个 Nucleo STM32H743 板之间实现 SPI 通信。
我已将 SPI 配置为全双工模式,启用 CRC,以最大 SPI 频率(SCK 频率为 100MHz)。
只要我只从主端传输和从从端接收,它就工作正常。
这是我用来从主机发送和从从机接收的代码。请注意,我知道这是快速而肮脏的代码,它只是一个概念证明,所以请不要评论编码风格(我知道,硬编码值,寄存器指针等......)。作为记录,我还使用了具有相同行为的 STMicro HAL 函数 HAL_SPI_Transmit
和 HAL_SPI_Receive
。
volatile unsigned long *CR1 = (unsigned long *)0x40013000;
volatile unsigned long *CR2 = (unsigned long *)0x40013004;
volatile unsigned long *TXDR = (unsigned long *)0x40013020;
volatile unsigned long *RXDR = (unsigned long *)0x40013030;
volatile unsigned long *SR = (unsigned long *)0x40013014;
volatile unsigned long *IFCR = (unsigned long *)0x40013018;
volatile unsigned long *TXCRC = (unsigned long *)0x40013044;
volatile unsigned long *RXCRC = (unsigned long *)0x40013048;
volatile unsigned long *CFG2 = (unsigned long *)0x4001300C;
void HAL_SPI_TransmitRegister(uint32_t Data, uint32_t Dummy)
{
// size of transfer (TSIZE)
*CR2 = 2;
// Enable SPI peripheral
*CR1 |= 1;
// Master transfer start
*CR1 |= SPI_CR1_CSTART;
// Fill the FIFO
*TXDR = Data;
*TXDR = Dummy;
while ( ((*SR) & SPI_FLAG_EOT) == 0 );
// clear flags
*IFCR = 0xFFFFFFFF;
// disable SPI
*CR1 &= ~1;
}
void HAL_SPI_ReceiveRegister(uint32_t *pData)
{
// two 32 bits words to be received
*CR2 = 2;
// Enable SPI peripheral
*CR1 |= 1;
while ( ((*SR) & (SPI_FLAG_EOT | SPI_FLAG_RXWNE)) == 0 );
*(pData) = (*RXDR);
while ( ((*SR) & (SPI_FLAG_EOT | SPI_FLAG_RXWNE)) == 0 );
*(pData+1) = (*RXDR);
// clear flags
*IFCR = 0xFFFFFFFF;
// disable SPI
*CR1 &= ~1;
}
所以上面的代码工作正常。现在这就是它开始变得有趣的地方:我想从奴隶那里传输一些东西。我修改了代码如下(也尝试了具有相同行为的函数 HAL_SPI_TransmitReceive
):
void HAL_SPI_ReceiveRegister(uint32_t *pData)
{
unsigned long response = 0xABCDEF99;
*CR2 = 2;
/* Enable SPI peripheral */
*CR1 |= 1;
// write response in advance
*TXDR = 0;
*TXDR = response;
// first 4 bytes are the command
while ( ((*SR) & (SPI_FLAG_EOT | SPI_FLAG_RXWNE)) == 0 );
*(pData) = (*RXDR);
// next 4 bytes are the dummy bytes used to send response
while ( ((*SR) & (SPI_FLAG_EOT | SPI_FLAG_RXWNE)) == 0 );
*(pData+1) = (*RXDR);
// clear flags
*IFCR = 0xFFFFFFFF;
// disable SPI
*CR1 &= ~1;
}
Master 收到乱码,但 Slave 收到正确数据。
现在STM32H7x3的datasheet确实提到了SPI在Slave模式下的限制,但我不明白"slave mode transmitter"是什么意思:
我的问题是:
数据表 "slave mode transmitter" 是什么意思?
我不明白为什么slave能够以100Mhz接收但不能发送,因为通信是全双工的。
如果数据仅单向流动,SPI 不必是全双工的。
当只有master在给slave发数据,slave没有数据要发时,软件不会在发送数据寄存器里写任何东西,还有东西 发送出去,因为根据定义,当 master 向时钟线发出脉冲时,输出位是 MISO 线上的任何内容。 SPI 从机不能像 I2C 从机那样延长时钟,数据 必须 在时钟脉冲到达时准备好发送。当发送寄存器为空时,SPI 控制器可能会发送全 0、全 1、重复最后一个字节或只是垃圾。
STM32H7 SPI 控制器可以配置为执行上述任何操作(以及更多),或者它可以配置为 只接收从机,MASTER=0
和 SPIx->CFG2
中的 COMM[1:0]=10
,则它不会在 MISO 上输出任何内容,除非将其重新分配给其他外围功能,否则该引脚将悬空。
相反,如果从站不关心主站在时钟脉冲时发送什么,它可以配置为仅传输从站,忽略 MOSI。在这种情况下,当没有人读取接收数据寄存器时,控制器既不会产生接收中断,也不会产生溢出错误。
作为SPI slave比master端更复杂
因为从站有两个时钟要遵守。来自控制发送和接收移位器的主机的外部时钟,以及用于访问寄存器的内部总线时钟。确保跨这些 时钟域 的数据完整性,即没有中途写入的数据从一个内部寄存器传输到另一个内部寄存器并非微不足道。 这就是为什么主从模式有不同的限制。
发送和接收移位器不一样。
他们有不同的任务要做,一个将数据移出,另一个将数据移入。可以实现一个同时对两个操作使用同一组触发器的方法,但它可能会变得太复杂上面的双时钟要求,所以有两个。 ST 的工程师发现,其中一个移位器在 29-31 MHz 以上无法可靠工作。只有他们才能判断这是一个无意的限制,还是这样做更简单(即更便宜),而且他们中没有大客户真正想要(即愿意为此多支付 0.0003 美元)此功能。