Skip to content
Snippets Groups Projects
Select Git revision
  • 677bd44b91a029eae3b34a29be8393b223851505
  • main default protected
2 results

2023-12-29_pi-spi-rates.md

Blame
  • 2023-12-29_pi-spi-rates.md 4.36 KiB

    2023 12 29

    OK, SPI is looking pretty good; actual bit times are identical to what-they-should-be, so I'm not even going to note them down (whereas UART out of the PI has some drift, probably strange BAUD fractional maths).

    SPI Waveform Integrity

    So, there's just the question of waveform integrity, and (probably) the ability of the RP2040 to keep up, and there's the actually-delineating-and-echoing-etc test.

    I'd like to look at some scope traces first, but I should probably do it in context (i.e. actually driving a pin, not free air), so I'll setup my little test rig...

    CH1 (yellow) is Chip Select
    CH2 (blue) is the Clock
    CH3 (magenta) is Data Dout

    Mbit/s Traces
    1 img
    2.5 img
    5 img
    10 img
    15 img
    20 img
    25 img
    50 img

    So the waveform basically looks like it survives all square-waved up to 5Mbit, then starts rounding off towards 10Mbit, and is probably serviceable between there and 25Mbit, where things are looking quite dicey indeed - as far as my relatively untrained eye can tell. By 100Mbit, all hope is lost.

    I suspect that "the move" here will be two-fold: (1) to do decent EE and line these traces with via-punched GND noise capture (to prevent the crosstalk we are seeing), and then to instrument each packet with some kind of CRC.

    I want to point out another benefit of the SPI route, which is that (if you look at the trace from 2.5Mbit) there is an inter-packet time of only 6 microseconds, so in our python sketch:

    import spidev 
    
    bitrate = 50000000
    
    print(f'rate {bitrate/1e6}MBit/s bit period should be {1000000000/bitrate}ns')
    
    spi = spidev.SpiDev()
    spi.open(0, 0)
    spi.max_speed_hz = bitrate 
    
    for i in range(1000000):
        spi.xfer([12, 14, 95])
    
    spi.close() 

    The time between spi.xfer()s is that few-microseconds of python returning to the top of the loop. If we look at similar code for UART:

    class CobsUsbSerial:
        def __init__(self, port, baudrate=115200):
            self.port = port
            self.ser = serial.Serial(port, baudrate=baudrate, timeout=1)
            self.buffer = bytearray()
    
        def write(self, data: bytes):
            data_enc = cobs.encode(data) + b"\x00"
            self.ser.write(data_enc)
    
        def read(self):
            byte = self.ser.read(1)
            if not byte:
                return 
            if byte == b"\x00":
                if len(self.buffer) > 0:
                    data = cobs.decode(self.buffer)
                    self.buffer = bytearray() 
                    return data
                else: 
                    return 
            else:
                self.buffer += byte 

    We are normally pulling one byte at a time - if we want to be catching while we are transmitting. I suspect that protocol work can avoid these mechanisms, but the other thing we don't have to deal with in the SPI case is packet framing: the CS line does that for us, we are COBS-less. The complexity ofc is that then we need to develop protocol between top- and bottom- layers to (probably) pack datagrams into fixed size frames (or something something) I digress.

    SPI Echo Code

    OK, so to see if we can get up to that 10Mbit real-transfer-rate target, I think I will start with an echo test - since SPI is transfer-based anyways (we always send data when we get it), I'll program some arduino to get packets and echo them, and we'll turn the speed up until we start making mistakes.

    I have hello-worlded this with the Earle Core, but their SPI implementation is strange - since it's simple enough (I'm starting to see that... that's the pint), I'll just roll my own... listening to CS down / up interrupts to frame packets, stuffing buffers, you know the dealio. It's 730pm here but that's just time to put the embedded hardo hat on...

    I'm going a bit mad with this; I can get GPIO interrupts to fire just on a falling edge but not just on a rising edge ... I should see if I can access some lower level masks, or something? But it's genuinely sending events marked as rising edge events on falling and rising edges...