STM32 Nucleo L432KC 和 MAX31865 ADC 之间通信的 SPI 额外时钟周期
我正在使用的设置是连接到 8 个不同的 MAX31865 ADC 的 Nucleo L432KC,以从 RTD(电阻热器件)获取温度读数。 8 个片选中的每一个都连接到自己的引脚,但所有芯片的 SDI/SDO 都连接到同一总线,因为我一次只读取一个(一次仅启用 1 个片选)。目前,我在开尔文连接中使用 100 欧姆基极电阻,而不是 RTD,只是为了确保准确的电阻读数。 RTD 的读取来自调用函数 rtd.read_all()。当我从一个且仅有一个 RTD 读取数据时,我得到了准确的读数和准确的 SPI 波形(粘贴在下面):
正确读取 1 个 ADC 的 SPI 读数 (黄色是芯片使能,绿色是时钟,蓝色是味噌,紫色是莫西)
但是,当我顺序读取2个或更多时,SPI时钟由于某种原因在读取开始时获得了额外的不需要的周期,从而导致了传输的值。它具有将时钟向右移动并将电阻值向左位移 1 的效果。
是什么导致了这个额外的时钟周期?我正在使用 mbed 用 C++ 进行编程。我可以访问 SPI.h 文件,但看不到实现,因此我不确定是什么导致了开始时出现这个额外的时钟周期。如果我也需要添加代码,请告诉我,我将编辑/评论。
rtd.read_all() 函数:
uint8_t MAX31865_RTD::read_all( )
{
uint16_t combined_bytes;
//SPI clock polarity/phase (CPOL & CPHA) is set to 11 in spi.format (bit 1 = polarity, bit 0 = phase, see SPI.h)
//polarity of 1 indicates that the SPI reading idles high (default setting is 1; polarity of 0 means idle is 0)
//phase of 1 indicates that data is read on the first edge/low-to-high leg (as opposed to phase 0,
//which reads data on the second edge/high-to-low transition)
//see https://deardevices.com/2020/05/31/spi-cpol-cpha/ to understand CPOL/CPHA
//chip select is negative logic, idles at 1
//When chip select is set to 0, the chip is then waiting for a value to be written over SPI
//That value represents the first register that it reads from
//registers to read from are from addresses 00h to 07h (h = hex, so 0x01, 0x02, etc)
//00 = configuration register, 01 = MSBs of resistance value, 02 = LSBs of
//Registers available on datasheet at https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
//The chip then automatically increments to read from the next register
/* Start the read operation. */
nss = 0; //tell the MAX31865 we want to start reading, waiting for starting address to be written
/* Tell the MAX31865 that we want to read, starting at register 0. */
spi.write( 0x00 ); //start reading values starting at register 00h
/* Read the MAX31865 registers in the following order:
Configuration (00)
RTD (01 = MSBs, 02 = LSBs)
High Fault Threshold (03 = MSBs, 04 = LSBs)
Low Fault Threshold (05 = MSBs, 06 = LSBs)
Fault Status (07) */
this->measured_resistance = 0;
this->measured_configuration = spi.write( 0x00 ); //read from register 00
//automatic increment to register 01
combined_bytes = spi.write( 0x00 ) << 8; //8 bit value from register 01, bit shifted 8 left
//automatic increment to register 02, OR with previous bit shifted value to get complete 16 bit value
combined_bytes |= spi.write( 0x00 );
//bit 0 of LSB is a fault bit, DOES NOT REPRESENT RESISTANCE VALUE
//bit shift 16-bit value 1 right to remove fault bit and get complete 15 bit raw resistance reading
this->measured_resistance = combined_bytes >> 1;
//high fault threshold
combined_bytes = spi.write( 0x00 ) << 8;
combined_bytes |= spi.write( 0x00 );
this->measured_high_threshold = combined_bytes >> 1;
//low fault threshold
combined_bytes = spi.write( 0x00 ) << 8;
combined_bytes |= spi.write( 0x00 );
this->measured_low_threshold = combined_bytes >> 1;
//fault status
this->measured_status = spi.write( 0x00 );
//set chip select to 1; chip stops incrementing registers when chip select is high; ends read cycle
nss = 1;
/* Reset the configuration if the measured resistance is
zero or a fault occurred. */
if( ( this->measured_resistance == 0 )
|| ( this->measured_status != 0 ) )
{
//reconfigure( );
// extra clock cycle causes measured_status to be non-zero, so chip will reconfigure even though it doesn't need to. reconfigure commented out for now.
}
return( status( ) );
}
The setup that I'm working with is a Nucleo L432KC connected to 8 different MAX31865 ADCs to get temperature readings from RTDs (resistive thermal devices). Each of the 8 chip selects is connected to its own pin, but the SDI/SDO of all chips are connected to the same bus, since I only read from one at a time (only 1 chip select is enabled at a time). For now, I am using a 100 ohm base resistor in the Kelvin connection, not an RTD, just to ensure an accurate resistance reading. The read from an RTD comes from calling the function rtd.read_all(). When I read from one and only one RTD, I get an accurate reading and an accurate SPI waveform (pasted below):
correct SPI reading for 1 ADC
(yellow is chip enable, green is clock, blue is miso, purple is mosi)
However, when I read from 2 or more sequentially, the SPI clock for some reason gains an additional unwanted cycle at the start of the read that throws off the transmitted values. It's been having the effect of shifting the clock to the right and bit-shifting my resistance values to the left by 1.
Logic analyzer reading of SPI; clock has additional cycle at start
What could be causing this extra clock cycle? I'm programming in C++ using mbed. I can access the SPI.h file but I can't see the implementation so I'm not sure what might be causing this extra clock cycle at the start. If I need to add the code too, let me know and I'll edit/comment.
rtd.read_all() function:
uint8_t MAX31865_RTD::read_all( )
{
uint16_t combined_bytes;
//SPI clock polarity/phase (CPOL & CPHA) is set to 11 in spi.format (bit 1 = polarity, bit 0 = phase, see SPI.h)
//polarity of 1 indicates that the SPI reading idles high (default setting is 1; polarity of 0 means idle is 0)
//phase of 1 indicates that data is read on the first edge/low-to-high leg (as opposed to phase 0,
//which reads data on the second edge/high-to-low transition)
//see https://deardevices.com/2020/05/31/spi-cpol-cpha/ to understand CPOL/CPHA
//chip select is negative logic, idles at 1
//When chip select is set to 0, the chip is then waiting for a value to be written over SPI
//That value represents the first register that it reads from
//registers to read from are from addresses 00h to 07h (h = hex, so 0x01, 0x02, etc)
//00 = configuration register, 01 = MSBs of resistance value, 02 = LSBs of
//Registers available on datasheet at https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
//The chip then automatically increments to read from the next register
/* Start the read operation. */
nss = 0; //tell the MAX31865 we want to start reading, waiting for starting address to be written
/* Tell the MAX31865 that we want to read, starting at register 0. */
spi.write( 0x00 ); //start reading values starting at register 00h
/* Read the MAX31865 registers in the following order:
Configuration (00)
RTD (01 = MSBs, 02 = LSBs)
High Fault Threshold (03 = MSBs, 04 = LSBs)
Low Fault Threshold (05 = MSBs, 06 = LSBs)
Fault Status (07) */
this->measured_resistance = 0;
this->measured_configuration = spi.write( 0x00 ); //read from register 00
//automatic increment to register 01
combined_bytes = spi.write( 0x00 ) << 8; //8 bit value from register 01, bit shifted 8 left
//automatic increment to register 02, OR with previous bit shifted value to get complete 16 bit value
combined_bytes |= spi.write( 0x00 );
//bit 0 of LSB is a fault bit, DOES NOT REPRESENT RESISTANCE VALUE
//bit shift 16-bit value 1 right to remove fault bit and get complete 15 bit raw resistance reading
this->measured_resistance = combined_bytes >> 1;
//high fault threshold
combined_bytes = spi.write( 0x00 ) << 8;
combined_bytes |= spi.write( 0x00 );
this->measured_high_threshold = combined_bytes >> 1;
//low fault threshold
combined_bytes = spi.write( 0x00 ) << 8;
combined_bytes |= spi.write( 0x00 );
this->measured_low_threshold = combined_bytes >> 1;
//fault status
this->measured_status = spi.write( 0x00 );
//set chip select to 1; chip stops incrementing registers when chip select is high; ends read cycle
nss = 1;
/* Reset the configuration if the measured resistance is
zero or a fault occurred. */
if( ( this->measured_resistance == 0 )
|| ( this->measured_status != 0 ) )
{
//reconfigure( );
// extra clock cycle causes measured_status to be non-zero, so chip will reconfigure even though it doesn't need to. reconfigure commented out for now.
}
return( status( ) );
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
背景:
我查看了 MAX31865_RTD 类的整个实现,我发现“麻烦”的是 MAX31865_RTD 实例在构造时创建自己的 SPI 实例。如果您创建该 MAX31865_RTD 类的多个实例,则将为每个实例创建并初始化一个单独的 SPI 实例。
如果您有 8 个这样的芯片,并创建 8 个独立的 MAX31865_RTD 实例来为每个芯片提供一个实例,那么这也会创建 8 个 SPI 实例,它们都指向微控制器的同一物理 SPI 设备。
问题:
当您在 MAX31865_RTD 实例上调用 read_all 函数时,它会依次调用 SPI 写入函数(如您提供的代码中所示)。但深入挖掘调用链,您最终会发现 SPI 写入方法(以及其他方法)的代码是以假设可以有多个 SPI 实例使用具有不同参数的相同 SPI 硬件的方式编写的(频率、字长等)。为了实际使用 SPI 硬件,SPI 类实例必须首先获得硬件的所有权(如果尚未拥有该硬件)。为此,它会“获取”自身硬件,这基本上意味着它将 SPI 硬件重新配置为该特定 SPI 实例设置的频率、字长和模式(无论每个实例都设置为他们彼此不了解。他们只是看到他们已经失去了所有权,因此必须重新获得它,并且他们也自动假设要恢复设置。)。这种频率(=时钟)重新初始化是您的时钟出现奇怪的人工制品/故障的原因。每次在不同的 MAX31865_RTD 实例上调用 read_all 时,该实例的 SPI 实例都必须执行获取操作(因为它们在每次 read_all 调用时相互窃取所有权),这会使时钟表现得很奇怪。
为什么它在您只有一台设备时有效:
因为当您只有一个 MAX31865_RTD 实例时,它就只有一个 SPI 实例,它是 SPI 硬件的唯一“所有者”。因此,没有人在每一回合窃取所有权。这意味着它不必在每次 read_all 调用时重新获取它。因此,在这种情况下,SPI 硬件不会每次都重新初始化。所以你不会得到奇怪的时钟脉冲,一切都会按预期工作。
我建议的解决方案#1:
我建议您更改 read_all 方法的实现。
如果您使用的 SPI 类版本具有 select 方法,则
在将片选 (nss) 拉低之前添加该行。基本上添加此块上方的行:
如果没有选择功能,则只需
在同一位置添加一行而不是带有选择的行。
本质上,所提议的两条线只是在片选线被断言之前强制采集(以及伴随的时钟毛刺)。因此,当片选被拉低并且实际数据被写入时,SPI 实例已经拥有所有权,并且写入方法将不会触发获取(也不会触发时钟毛刺)。
我建议的解决方案#2:
另一个解决方案是修改 MAX31865_RTD 类以使用 SPI 实例引用,并通过其构造函数提供该引用。这样,您就可以显式创建一个 SPI 实例,并在构建时向所有 MAX31865_RTD 实例提供相同的 SPI 实例。现在,由于所有 MAX31865_RTD 实例都使用对同一且唯一 SPI 实例的引用,因此 SPI 硬件所有权永远不会改变,因为只有一个 SPI 类实例正在使用它。因此,硬件永远不会重新配置,故障也永远不会发生。我更喜欢这个解决方案,因为它不是一个解决方法。
我提出的解决方案#3:
您还可以修改 MAX31865_RTD 类,为 nss(= 芯片选择)引脚提供一个“设置器”。这样,您的所有 8 个器件就可以只有一个 MAX31865_RTD 实例,并且在寻址下一个器件之前仅更改 nss 引脚。由于只有一个 MAX31865_RTD 实例,因此也只有一个 SPI 实例,这也解决了所有权问题,并且由于无需重新获取,因此不会触发任何故障。
当然,在了解问题原因的情况下,还可以通过多种其他方法来解决此问题。
希望这有助于解决您的问题。
Background:
I took a look at the entire implementation of the MAX31865_RTD class and the thing I find "troubling" is that a MAX31865_RTD instance creates its own SPI instance on construction. If you create multiple instances of this MAX31865_RTD class then there will be a separate SPI instance created and initialized for each of these.
If you have 8 of these chips and you create 8 separate MAX31865_RTD instances to provide one for each of your chips then this also creates 8 SPI instances that all point to the same physical SPI device of the microcontroller.
The problem:
When you call the read_all function on your MAX31865_RTD instance it in turn calls the SPI write functions (as seen in the code you provided). But digging deeper in the call chain you will eventually find that the code of the SPI write method (and others as well) is written in a way that it assumes that there can be multiple SPI instances that are using the same SPI hardware with different parameters (frequency, word length, etc...). In order to actually use the SPI hardware, the SPI class instance must first take ownership of the hardware if it does not have it yet. To do this it "acquires" the hardware for itself which basically means that it reconfigures the SPI hardware to the frequency and word length and mode that this particular SPI instance was set to (This happens regardless of the fact that every instance is set to the same parameters. They don't know about each other. They just see the fact that they have lost ownership and thus have to reacquire it and they also automatically assume that the settings are to be restored.). And this frequency (= clock) reinitialization is the reason that your clock is having a weird artefact/glitch on it. Each time you call the read_all on a different MAX31865_RTD instance the SPI instance of that instance will have to do an acquire (because they steal the ownership from each other on each read_all call) and it will make the clock behave weird.
Why it works if you only have one device:
Because when you have one and only one MAX31865_RTD instance then it has only one SPI instance which is the sole "owner" of the SPI hardware. So no-one is stealing the ownership on each turn. Which means that it does not have to re-acquire it on every read_all call. So in that case the SPI hardware is not reinitialized every time. So you don't get the weird clock pulse and everything works as intended.
My proposed solution #1:
I propose that you change the implementation of the read_all method.
If the version of the SPI class that you use has the select method, then add the
line just before pulling the chip select (nss) low. Basically add the line above this block:
If there is no select function, then just add a
line in the same place instead of the line with the select.
In essence both of the proposed lines just force the acquire (and the accompanying clock glitch) before the chip select line is asserted. So by the time the chip select is pulled low and the actual data is being written the SPI instance already has ownership and the write methods will not trigger an acquire (nor the clock glitch).
My proposed solution #2:
Another solution is to modify the MAX31865_RTD class to use an SPI instance reference and provide that reference through its constructor. That way you can create one SPI instance explicitly and provide this same SPI instance to all your MAX31865_RTD instances at construction. Now since all of your MAX31865_RTD instances are using a reference to the same and only SPI instance, the SPI hardware ownership never changes since there is only one SPI class instance that is using it. Thus the hardware is never reconfigured and the glitch never happens. I would prefer this solution since it is less of a workaround.
My proposed solution #3:
You could also modify the MAX31865_RTD class to have a "setter" for the nss (= chip select) pin. That way, you could have only one MAX31865_RTD instance for all your 8 devices and only change the nss pin before addressing the next device. Since there is only one MAX31865_RTD instance then there is only one SPI instance which also solves the ownership issue and since no re-acquisition has to be made then no glitch will be triggered.
And of-course there can be any number of other ways to fix this knowing the reason of the problem.
Hope this helps in solving your issue.