I noticed an interesting thing with ESP8266 SPI, given a simple function to test writing bytes like:
import nativeio
import board
spi = nativeio.SPI(clock=board.SCK, MOSI=board.MOSI, MISO=board.MISO)
while not spi.try_lock():
pass
spi.configure(baudrate=1000000)
cs = nativeio.DigitalInOut(board.GPIO0)
cs.switch_to_output(value=True)
def test_spi(spi, cs):
cs.value = False
spi.write(bytearray([255, 128, 64, 32]))
cs.value = True
I notice if I setup a Saleae and look at the pin output the CS pin toggles back up to true before the entire SPI write has finished going out the clock and MOSI lines. See below:

The top line is the clock, middle line is MOSI, and bottom line is CS. Notice how 255, 128, 64 come through correctly but the last byte 32 isn't interpreted because the CS line (bottom row) jumps back up to a high value before the last byte finishes writing out the SPI lines.
It seems like perhaps the SPI data is buffered and we're not waiting for the buffer to finish draining before returning. It becomes problematic though in this case where you toggle CS and other lines around SPI transactions since you need to wait for the data to finish sending/receiving before changing those lines.
If I put in an explicit sleep before toggling CS low I do see the byte received as expected:

Let's see if there's a bit to tell if the SPI buffer is drained and perhaps busy wait on that before returning from SPI.write.
I noticed an interesting thing with ESP8266 SPI, given a simple function to test writing bytes like:
I notice if I setup a Saleae and look at the pin output the CS pin toggles back up to true before the entire SPI write has finished going out the clock and MOSI lines. See below:

The top line is the clock, middle line is MOSI, and bottom line is CS. Notice how 255, 128, 64 come through correctly but the last byte 32 isn't interpreted because the CS line (bottom row) jumps back up to a high value before the last byte finishes writing out the SPI lines.
It seems like perhaps the SPI data is buffered and we're not waiting for the buffer to finish draining before returning. It becomes problematic though in this case where you toggle CS and other lines around SPI transactions since you need to wait for the data to finish sending/receiving before changing those lines.
If I put in an explicit sleep before toggling CS low I do see the byte received as expected:

Let's see if there's a bit to tell if the SPI buffer is drained and perhaps busy wait on that before returning from SPI.write.