I'm using a STM32 DISCO-L072CZ-LRWAN1 with an MBED OS based minimal echo test firmware as SPI slave while using a Arduino (Seedstudio Seeduino with 3V3 IO) as SPI master.
Minimal-Test-Code
Wiring
| ARDUINO | MBED |
|:-:|:-:|
| D11 - MOSI | PB15 - MOSI |
| D12 - MISO | PB14 - MISO |
| D13 - SCK | PB13 - SCLK |
| D10 - CS | PB12 - SSEL |
| GND | GND |
Desired result
Each byte written from master to slave should be returned with an delay of one byte.
Actual result
The returned bytes are all over the place - there is no systematic correlation between transmitted message of master and received message by slave.
(RESET)
12 -> 00 | 34 -> 00
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 34 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 34
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 34
12 -> 12 | 34 -> 34
12 -> 12 | 34 -> 34
12 -> 12 | 34 -> 34
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 34
12 -> 12 | 34 -> 34
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 12
12 -> 12 | 34 -> 34
12 -> 12 | 34 -> 34
[ ] Question
[ ] Enhancement
[X] Bug
Just a few thoughts. ...
What is the SPI frequency you're running at and can you try to lower it down ? L072 is relatively small, low frequency chip, and mbed-os SPI slave is full SW, so it might it too slow to answer in time to master requests.
Note that will be a few things may influence. Have you configured the same 16bits or 8 bits transfer width on both sides ?
Also when master sends the first byte, slave will read it. Only when the 2nd byte is sent by master, the slave can answer with the first byte that was received ... and so on. so you'd actually need to read one more byte from master to receive the 2nd byte from slave to master ...
If lowering down the SPI frequency on master side, doesn't help, I think we may need a logic analyzer trace to help further ... this would clarify the above points.
Hi @LMESTM , thank you for your input. I'd like to address it point-by-point.
What is the SPI frequency you're running at and can you try to lower it down ? L072 is relatively small, low frequency chip, and mbed-os SPI slave is full SW, so it might it too slow to answer in time to master requests.
I've reduced the clock speed to 125/250/500 kHz and explicitly configured the SPI settings to 8 Bits, SPI Mode 0 (those are supposed to the defaults on each platform anyway). While reducing the SPI frequency reduces occurences of wrong replies, it does not eliminate them.
Also when master sends the first byte, slave will read it. Only when the 2nd byte is sent by master, the slave can answer with the first byte that was received ... and so on. so you'd actually need to read one more byte from master to receive the 2nd byte from slave to master ...
Since the same SPI access happens over and over the 2nd byte written during the 1st access should be received when the first byte of the 2nd acces is written. I'll illustrate it with a small table in order to better show what I mean:
| SPI Transfer | | | | | | | |
|-|-|-|-|-|-|-|-|
| 1 | MOSI | 0x12 | 0x34 | | | | |
| 1 | MISO | 0x00 | 0x12 | | | | |
| 2 | MOSI | | | 0x12 | 0x34 | | |
| 2 | MISO | | | 0x34 | 0x12 | | |
| 3 | MOSI | | | | | 0x12 | 0x34 |
| 3 | MISO | | | | | 0x34 | 0x12 |
As a result the log should look like this:
(RESET)
12 -> 00 | 34 -> 12
12 -> 34 | 34 -> 12
12 -> 34 | 34 -> 12
12 -> 34 | 34 -> 12
Unfortunately, this is not what the log says.
Again, a logic analyzer trace would definitely help.
If not possible, we're a bit blind ... could you try removing the CS pin toggling from your Arduino code ? The Slave side code tries to reply to reply to the 2nd byte received, while the master will de-assert the line ... I'm not sure what the effect will be.
Or I'd rather see a sequence like :
I don't see why your proposed sequence should make a difference. During a single byte SPI transfer both master and slave perform simultaneously a read and a write operation. Therefore if the master should "read" a 3rd byte it has to "write" a 3rd byte too. The way the SPI slave echo firmware is implemented, slave always replies with the byte received from master in the previous SPI transfer (with the exception of the SPI first transfer, in which case the reply is 0). The assertion/deassertion of CS should not interfere with this behaviour.
We need some ground truth. I'll hookup a logic analyser and get a trace.
It took a little while until my Salae Logic 8 arrived but finally here it is - the logic analyser trace of the SPI communication which clearly show that not always the currect byte is echoed back.
Everything starts quite as expected ...
| SPI Transfer 0 | OK | |
|-|-|-|
| MOSI | 0x12 | 0x34 |
| MISO | 0x00 | 0x12 |

| SPI Transfer 1 | OK | |
|-|-|-|
| MOSI | 0x12 | 0x34 |
| MISO | 0x34 | 0x12 |

But at some point in time we have exactly the problem already shown in the serial output - the wrong byte is echoed back.
| SPI Transfer 27 | OK | |
|-|-|-|
| MOSI | 0x12 | 0x34 |
| MISO | 0x34 | 0x12 |

| SPI Transfer 28 | FAIL | |
|-|-|-|
| MOSI | 0x12 | 0x34 |
| MISO | 0x34 | 0x34 |

Within the mbed SPI slave there is happening nothing else besides this
for(;;) {
if(spi_slave.receive()) {
spi_slave.reply(spi_slave.read());
}
}
which is why I expect that this should always work as advertised, especially with a SPI clock as low as 125 kHz.
I've added the Salae and exported VCD trace files for closer inspection.
spi-slave-echo-logic-analyser-trace.zip
Thanks for this !
Even @ 125kHz, we can see that there is only 2碌s between the end of the first byte and the begining of the second byte. That means that the slave has to cope with reading this first byte and writing it back to the SPI register within those 2 碌s. Any other interrupt like Timer interrupt or OS core scheduling operation may cause this operation to fail which probably happens here.
Because the write obviously failed to happen in the expected timeframe, then the SPI HW will send back the same information that is present in the register (0x34 here).
To make this work reliably I think the master needs to add some delay between the 2 characters. Or the messaging protocol should take this into account. For instance master sends 3 bytes, and slave will report back N-2 byte - which would leave 1 full byte transfer for sending back, which is around 60碌s and seems more reasonable.
I agree that 2 碌s is a small turnaround time, however, how long should a delay be? I've tried with a delay of 1 ms between two consecutive SPI transfers:
uint8_t const reply1 = SPI.transfer(0x12);
delayMicroseconds(1000);
uint8_t const reply2 = SPI.transfer(0x34);
Despite this delay I do experience the same issues as before. Furthermore, a delay of >= 1 ms between two consecutive SPI transfers eliminates the speed advantage the synchronous interface SPI has over asynchronous interfaces such as UART.
EDIT - Adding Logic Trace for whoever wants to take a look - spi-slave-la-trace-delay-1-ms.zip
Stuffing a delay/buffer byte between request/response is just treating the symptoms, not the cause. If I read more than one byte (which is what I really want - the code used throughtout this issue is really just the smallest possible example to demonstrate incorrect behaviour) I'm again left with the question how to fill the reply buffer in time to have the correct data transmitted from slave to master. I'd need a space of at least one byte again between every returned byte, effectively halfing the data rate.
In the end, regardless how long the delay is, any interrupt or other system activity could always be just a bit too long for the transfer buffer to be filled reliably. This means that the SPI slave implementation in its current state is unusable for serious use.
A possible way out would be if one could register a interrupt which is triggered upon a complete transfer. In this case one just has to hope that no higher priorized interrupt is interupting the SPI ISR handler as well as any duration during which interrupts are blocked (be it execution of higher prio ISRs or critical sections) is small enough to still have enough time to fill up the reply buffer in time.
cc @ARMmbed/mbed-os-core for feedback and any recommendations on SPI APIs usage
@MarceloSalazar can you find someone from mbed-os team to help answering ? Maybe it's HAL team rather than core team ?
@ARMmbed/mbed-os-hal has some updates on spi feature branch so this issue should be considered for this feature
@lxrobotics Would you have a look at the mbed-os SPI feature branch and see if that helps ?
@LMESTM Thank you very much for pursuing this matter. I'm currently on a parental leave of absence and will take a look at feature-hal-spec-spi as soon as I get back.
Hi @LMESTM Laurent, feature-hal-spec-spi looks like it has the features I'm looking for. Is there any update on when those features will be merged into master?
@lxrobotics not my decision when this gets merged.
@ARMmbed/mbed-os-hal any target date ?
@adbridge @0xc0170
Should we keep this issue opened as long as spi feature branch is not merged ?
Thx
@jeromecoutant we probably should. I do not know when/if the spi feature will land on Master.
Wouldn't hurt if could be kept in the loop somehow - there's still interest from my side to use the functionality enclosed in the spi feature branch.
Thank you for raising this detailed GitHub issue. I am now notifying our internal issue triagers.
Internal Jira reference: https://jira.arm.com/browse/IOTOSM-2318