-
Notifications
You must be signed in to change notification settings - Fork 980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
spi transfer16 is not 16 bit #2514
Comments
Yes, notice that they are derived from the same earlier version, The earliest version that I found, ran on the uno, an 8bit 16MHZ legacy platform marketed to hobbyists. Adapting it in form to the much faster 16 and 32 bit platforms, leaving the internal kluges and inefficiencies in place was amateur and hurried, and the result is simply not correct in many places. (I am referring to the history of this code, not a specific person). Two errors that require urgent attention are: (a) 16 bit transfers implemented as 8 bit transfers, and (b) failure to also provide a loop friendly interface (i.e., separate calls for setup and transfer, rather than forcing setup and transfer into every call) The erroneous implementation of 16 bit transfers as two 8 bit transfers, alone, reduces performance by something like a factor of 2 or more. Forcing setups into very transfer and not providing a loop friendly API, reduces performance by a solid factor of 8. The result is that the API is therefore incompatible with a large class of important peripherals; c.f., any device that requires a pin assertion between transfers and better than 250KSPS transfers. An ADC reading a linear CCD would be a good example. Sometimes there has been push back on fixing this, it is usually about the transaction interface, and it usually comes from that same hobbyist crowd. The logic does not hold up. Reserving the device, even pushing the settings onto a stack, if you think you must despite it being an on-the-metal platform, should not be effected by what you do with it once it is reserved. Concluding this introduction, lets note that limiting a 200MHz MCU with a 50MHZ SPI clock to 250ksps transfers, only because the code was inherited from a slow 8 bit legacy platform that catered to hobbyists, does not make a lot of sense and it is not good for the platform. My solution for that for the UNO R4, is here . There is also a solution for the Teensy 4.x, the discussion is here In both of those, the patch (a) implements a true 16 bit transfer, and (b) provides two new calls, one for the setup and another for the actual transfer. A loop in a user program can look like this:
Note that with a 50MHZ SPI clock, this could run at close to 1MSPS. The STM32 could be a serious platform. It deserves a little more attention to the API for the SPI, one of its most important peripheral interfaces. (BTW The original title referred also to overheads. Note that is indeed part of the problem. But feel free to choose a title that makes sense.) |
P/S, i forgot, in the loop-friendly API, you may want a "restore" as well. Looking at the transfer times. I see there is only 400 nsecs between the two 8 bit transfers, but 1200nsecs before and 1100nsecs after. So that means a correct loop-friendly call, besides the 16bits/50MHZ, only needs an additional 400nsecs. |
Main issue with your setup/transfer api's is it is not Arduino api compatible. |
What makes it incompatible? a) Those calls that I suggest, are what is missing from the Arduino API to make it actually useful. b) You already added new calls and functionality to the API, setSSEL() etc. |
Looking at the code again, if you could save me a little time and explain what the conditional compiles are about in this, I might try a patch and you can see if you like it enough for a PR. Arduino_Core_STM32/libraries/SPI/src/utility/spi_com.c Lines 482 to 550 in 89a2951
|
Well several examples already exists, if new one are created using those new API they will make it incompatible with other core.
While new API simply change the way you implement the SPI transaction. About the "conditional compiles", it is simply to have the code working for each STM32 series which can have different SPI IP and so required different configuration or API. |
I am sorry, that is not correct at alll. Incompatibility means that something you add or change, causes something in the existing API to no longer meet it's specification. Think of it in terms of code that uses the library: As long as existing code that uses the library continues to work to spec, then an added function cannot be said to be incompatible. You have undoubtedly seen the term "backward compatibility". In this case, transfer16() was one such example, I remember when it was added. Teensy did it correctly, Arduino did not. But both are lacking a way to run an efficient loop. As long as you insist on this very incorrect position, your SPI implementation will be limited to taking 5 usecs for transfers that should take 320 nsecs. Aside, your SPI.setxxx() would of course be backward compatible. And I am very interestred in setSSEL(), but unfortunately when I add that call to my setup(), it crashes. P/S Adding SPI.transfe16_setup() and SPI.transfer16_transfer(), will of course help sell the board and your API. The 50MHz SPI is a big deal, it is a terrible shame that is squandered by stopping short when you already did so much work and you are already so close. |
My concern is about the example (sketch) which "should" as much as possible be compatible between core. This is the way Arduino works. That's all 😉
I don't insist 😉 we discuss. I never told it will not be implemented. This is a community project, any contribution are welcome.
See my answer on the forum -> https://www.stm32duino.com/viewtopic.php?p=14825#p14825
Why? First goal was to provide support for STM32 series in Arduino with Arduino API references. I know it provides limitations and it could be improved but as always time is the missing ingredient. Several other features required to be implemented or enhanced., do my best. As stated any contribution are welcome. |
Correcting transfer16() to do 16bit transfers, and adding loop friendly calls should not change the way the existing example works.
Yes I apologize for sounding argumentative. There is a lot of resistance and appeal to dogma in the community. The transaction api is a good example.
Thank you, I am in the lab at the moment and dont have that log-in. I see that it was the wrong pin., no doubt a result of failing to digest the datasheet. Are D13, D12, D11 then not the pins that it is using for the SPI? My intent was to use the next pin, as is customary, Also I wasnt sure which SPI it is using, it seems like SP1,
Because that it is what enables 1MSPS reads and that ties into a large swath of applications that are important in using arduino class boards for doing science.
My philosophy is that while I might not implement everything, I try to at least, not introduce any limitation not present in the hardware. So there is a difference between fully supporting a rich set of features provided by the hardware, and for that I agree, who has time. Unnecessarily choking off performance is something else, and that is what the historic SPI library does. Fortunately, it can be fixed with a patch or two plus a few extra calls that do not disturb the API already in place. |
Are you using CLA for PR's? If not, then I might take a look at patching the library, perhaps even today. It looks like you did a good job with factoring, so it seems like it should be easy. |
D11 to D13 are usually the default SPI pins (for boards with Arduino UNO connector) and D10 used a CS. But in a general way CS is managed by the application (software). When you used To go deeper, when a board have a Arduino connector, default SPI pins are D10 to D13: Arduino_Core_STM32/cores/arduino/pins_arduino.h Lines 43 to 65 in 89a2951
In our case for Nucleo F722: Arduino_Core_STM32/variants/STM32F7xx/F722Z(C-E)T_F732ZET/variant_NUCLEO_F722ZE.h Lines 29 to 31 in 89a2951
Thanks to the PeripheralPins.c :
So to answer your question SPI uses Finally, you can check in the below table which SPI SSEL to use: Arduino_Core_STM32/variants/STM32F7xx/F722Z(C-E)T_F732ZET/PeripheralPins.c Lines 338 to 349 in 89a2951
No. Only follow the CONTRIBUTING.md |
Thank you, quite elaborate and seems like several layers of naming. So it seems like SPI1 uses PA_5,PA_6,PA_7 and PA_4 for CS. What is confusing then, is this from the user manual (UM1974), page 58, where it says the arduino SPI uses PA5, PA6, PA7, as D13,D12,D11, and for D10 it is not PA4, but rather PD14 So, bottom line, what goes in the call to setSSEL(), and what pin does it appear on? Thank you |
Well, if PD14 is described as SPI1_CS then there is a mistake in the datasheet or in the stm32 open pin data: https://github.com/STMicroelectronics/STM32_open_pin_data/blob/f4ec11f00e762e37ffc4020f6d4f20d225bc061d/mcu/STM32F722Z(C-E)Tx.xml#L626-L632 The PeripheralPins.c is generated from this file that's why PD14 is not in the SPI SSEL array. My guess is the Nucleo F722ZE datasheet simply tell that PD14 can be used for CS on the Arduino connector to be compatible. As Arduino manages CS by software (gpio toggle) and not by hardware (manage by the SPI IP itself).
About this it is not page 32 but 35 even if several pins match. |
Yes that was the table that I meant to cite. |
Okay, so the code compiles and runs using PA_4 or D24. i.e. SPI.setSSEL(PA_4) and SPI.setSSEL(24), both compile and run. In the following, the traces from top to bottom are yellow = D13 (SCK), blue = D12 (MISO), red = D11 (MOSI), and green = PA_4 (D24). Notice that the first time the SPI is called, the MOSI is low and goes high for the transfers. That is actually not correct, It needs to be high when not active. The second time the SPI loop is invoked, MOSI stays high. So it is not consistent,. The PA_4 pin (SSE)L does nothing. And here is the interface using D10 for the CSEL, under program control. You can see the MOSI again starts low, and then goes to the high state after the second pair of 8 bit transfers. |
Okie dokie, Here is an implementation for 16 bit transfers. The attached is a zip of the SPI/src tree with the 16 bit transfer implemented. I left the old routine in place with a suffix on the name. The mod for the 16 bit transfer is pretty simple, really. Does it look right? The good news is that there seems to be no per-transfer setup that could be optimized out of the call for loops. So there is nothing that requires or would benefit from a loop friendly API. (I am still testing this, so please consider it preliminary). There are two items of less good news. A) The overheads anyway are large, and for the one before transfer starts it is not a constant delay. So that is a big deal for some real time applications. I think the delays comes from here, does that seem right? Arduino_Core_STM32/libraries/SPI/src/utility/spi_com.c Lines 502 to 507 in c888cf9
Arduino_Core_STM32/libraries/SPI/src/utility/spi_com.c Lines 510 to 514 in c888cf9
Aside, for the ADC, there is a pin CNVST (connected to the SSEL line) that starts the conversion, conversion then takes 700nsecs and the CNVST pin needs to be cleared at least 30 nsecs before readout. That means to get best performance, I would have to hack that into the transfer routine, i.e. set it, do the idiot loop for the interface ready flag, then clear cnvst, and start the transfer. Doable, but tedious. B) The MOSI line is, well, flakey, and not completely correct. I am guessing there is a setup or config somewhere that controls the idle state for the MOSI line, is that correct? Here is the aberrant behavior that needs to be fixed: i) The apparent idle state before the first call is low (should be high) ii) For txdata, it goes high momentarily for the first transfer, that is half-correct (see above, i) iii) For the second transfer it goes high and stays high. It stays high after this. The correct or at lease more conventional, behavior for txdata 0xFFFF, of course, is that it starts high and stays high. |
Please create a PR in draft. Archive is really not efficient to track and comment. |
The PR draft is submitted. I also want to take up the issue with the MOSI pin state. That too is critical. Shall I make a new issue for it? |
No. It is not a bug. MOSI pins kept the last value level. We have no way to specify the level when CS is not active. And see no reference it should be high in SPI documentations. It is simply "don't care" |
Describe the bug
SPI transfer16() produces two 8 byte transfers rather than one 16 bit transfer.
To Reproduce
Here is a code that is used for testing SPI transfers. In this instance, the CS pin tells an ADC to convert its input to digital, then 730 nanoseconds later the data is ready for a 16 bit SPI transfer.
With an SPI clock of 50kHz, this should be able to run at close to 1MSPS.
`
And here is the oscilloscope trace. Notice that rather than one 16 bit transfer, we have two 8 bit transfers. And note that they are separated by 400 nsecs, but there is 1200 nsecs before and after.
Here is the implementation, for transfer16() it calls spi_transfer( .... length )
Arduino_Core_STM32/libraries/SPI/src/SPI.cpp
Lines 179 to 195 in 89a2951
And, in utility/spi.c, we find that spi_transfer() simply loops over LL_SPI_TransmitData8( ).
Arduino_Core_STM32/libraries/SPI/src/utility/spi_com.c
Lines 482 to 550 in 89a2951
You already have a LL_SPI_TransmitData16( ), under hardware/stm32/2.8.1/system/Drivers/
What is missing in this implementation is a spi_transfer16() and to change transfer16() to call it.
Thank you
The text was updated successfully, but these errors were encountered: