SPI Transaction Length Configurability

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
guanabana
Posts: 15
Joined: Mon Jan 04, 2016 8:45 pm

SPI Transaction Length Configurability

Post by guanabana » Fri Jan 08, 2016 1:27 am

Does anyone happen to know if it is possible to configure the length of SPI transactions so that they aren't strictly integer multiples of 8b?
Say I wanted to send a frame of 9 or 13 bits, is it possible to do that in upy with the pyboard?

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: SPI Transaction Length Configurability

Post by dhylands » Fri Jan 08, 2016 1:35 am

Looking through the datasheet I only see support for sending multiples of 8 or 16 bits.

It depends on what you're trying to do. If you don't care about the clock and just want the data to change you could run the clock 8x faster (if that rate is supported) and send out sequences of 0x00's or 0xFF's. Each byte would look like a bit. Most SPI devices that I've seen tend to use byte interfaces.

guanabana
Posts: 15
Joined: Mon Jan 04, 2016 8:45 pm

Re: SPI Transaction Length Configurability

Post by guanabana » Wed Jan 20, 2016 6:36 pm

Thanks Dave for the response and suggestion.
I was scoping out repurposing the SPI hardware peripheral to work with some other rando comm protocol but I have concluded that it isn't really a workable idea because it has frame-lengths that aren't multiples of 8b (and I would need the clock), the data line is bidirectional, and bit banging is 3 orders of magnitude too slow for what I need (and some other reasons) so I've realized that it is best to implement in an FPGA.
Thanks,
Steve

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: SPI Transaction Length Configurability

Post by dhylands » Wed Jan 20, 2016 6:46 pm

Bit banging in python? Or bit banging in C/asm?

It is possible to write asm code using MicroPython. Here's an example I did (there are a bunch of others on the forum):
https://github.com/dhylands/bioloid3/bl ... ort.py#L59

guanabana
Posts: 15
Joined: Mon Jan 04, 2016 8:45 pm

Re: SPI Transaction Length Configurability

Post by guanabana » Sat Jan 23, 2016 8:21 pm

Alright, I was interested to give it a shot in assembly and got this almost sort of working.

The order that the word from the byte array prints out (if I were to just return ldr(r0, [r0, 0]) ) is backwards from what I would expect (if the words of the source byte array weren't symmetric like it is in this example), but maybe an endianness thing?

Seems like I would just need to add a loop to iterate through successive words of the byte array and it should just let me toggle an IO as fast as I want?

Code: Select all

from uctypes import addressof
DELAY = const(2000000)

@micropython.asm_thumb
def write_data_twice(r0):           # r0=addrOfArray with data to send
    movwt(r2, stm.GPIOA)            # GPIOA addr in r1. 3 LEDs on GPIOA
    ldr(r3, [r0, 0])                # 1st 16b half-word from bytearray (bit mask for LEDs)

    # turn on LEDs
    strh(r3, [r2, stm.GPIO_ODR])

    # delay for t=DELAY
    movwt(r4, DELAY)
    label(delay_on)
    sub(r4, r4, 1)
    cmp(r4, 0)
    bgt(delay_on)

    # turn LED off
    ldr(r3, [r0, 4])    # load 2nd half-16b word from bytearray
    strh(r3, [r2, stm.GPIO_ODR])

b = bytearray(12)
b[0] = 0xee
b[1] = 0xee
b[2] = 0xee
b[3] = 0xee

addrOfDataToSend = addressof(b)
write_data_twice(addrOfDataToSend)

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: SPI Transaction Length Configurability

Post by pythoncoder » Mon Jan 25, 2016 7:20 am

Unless I'm missing something

Code: Select all

ldr(r3, [r0, 4])
is loading r3 with a 32 bit word from undefined memory, past the end of the bytearray.
Peter Hinch
Index to my micropython libraries.

guanabana
Posts: 15
Joined: Mon Jan 04, 2016 8:45 pm

Re: SPI Transaction Length Configurability

Post by guanabana » Mon Jan 25, 2016 8:39 pm

Typo. But here is what I ended up with and it actually works up to 12MHz if you comment out the delay block:

Code: Select all

from uctypes import addressof

@micropython.asm_thumb
def write_io_asm(r0, r1):  # r0=addrOfArray with data, r1=nBitsToSend
    mov(r4, 0)  # init bit counter

    movwt(r3, stm.GPIOA)    # GPIOA addr in r1 (bc 3 LEDs on GPIOA)

    label(loop1)       # loop entry

    ldr(r2, [r0, 0])    # load bit mask from bytearray addr in r0 to reg r2 used as 'output data reg'
    add(r0, 4)          # r0 is addr of bytearray[i], r0+4 is addr of bytearray[i+1]

    # write data[i] from bytearray
    strh(r2, [r3, stm.GPIO_ODR])  # r3 is GPIOA, r2 is bit mask from bytearray[i]

    add(r4, 1)          # increment loop ctr

    # delay
    movwt(r5, 20000000)
    label(delay_on)
    sub(r5, r5, 1)
    cmp(r5, 0)
    bgt(delay_on)

    # loop r1 times
    sub(r1, r1, 1)
    cmp(r1, 0)
    bgt(loop1)

    mov(r0, r4)         # bit counter to r0 to print (since asm returns r0)

dat=bytearray(10)
dat[1]=0xe0	# bit mask for orange, green,red
dat[5]=0x80	# orange only
dat[9]=0x40	# green only
dataToSend=addressof(dat)
print('write_io_asm', write_io_asm(dataToSend, 3))
The thing I don't understand is why dat[1]=0xe0 turns on the LEDs on PA13/14/15 instead of dat[0], but I'll start another tread on that.

Thanks,
Steve

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: SPI Transaction Length Configurability

Post by dhylands » Mon Jan 25, 2016 9:14 pm

The ODR register is 16 bits wide and is stored in little-endian order.

So bits 0-7 will be at the address GPIOA + GPIO_ODR and bits 8-15 will be at the address GPIOA + GPIO_ODR + 1

psupine
Posts: 1
Joined: Tue May 24, 2016 4:52 am

Re: SPI Transaction Length Configurability

Post by psupine » Tue May 24, 2016 11:45 am

Hello .. I'm new here.

I've been trying to use a v1.1 Pyboard to talk to a 16x2 OLED display over SPI.

I changed the links on the display module to configure it for SPI. This involved moving two 0R resistors. The display datasheet showed an awkward 10 bit interface where the two MSBs are used to select an instruction or data command, and whether it is issuing a readback. I got this protocol working using slow motion Python mode bit banging.

When I tried to convert it to use the SPI hardware I initially tried to set "bits=10", but looking at the microPython code on GitHib shows that it checks if the parameter is 16, and otherwise sets it to 8. No joy. The reason for this restriction is that the ST microcontroller only supports 8 bit and 16 bit SPI frames.

So then I figured that perhaps the interface would cope with a 16 bit transfer, where a de-asserted chip select would tell the display to ignore the last 6 bits clocked into its shift register. I first checked this by modifying my working 10 bit bit-banger to clock an extra 6 zeros onto the end. Yep, it still worked like that.

Then I tried to replicate this transfer with 16 bit SPI transfers. I certainly saw 16 clocks per frame, but the serial data was really inconsistent. Finally I wrote a loop that just hammered a 16 bit pattern of 0xCCaa, because it would be recognisable on the scope and let me verify endian-ness.

What I saw was 0x00aa, so it seemed that the spi driver still wanted to see bytes. So I went back to 8 bit mode on the initialisation, and fed the send method a two byte bytearray with the most significant byte in slot [0]. The oscilloscope now showed the right number of clocks and the right serial data pattern, but the setup timing was wrong. Finally, I changed the initiialisation parameter 'phase' to 1 and it came to life. Then I cranked up the baud rate.

Here are the incantations that worked for me:

In initialise ...
self.spi = SPI(1, mode=SPI.MASTER, baudrate=1228800, polarity=1, phase=1, bits=8, firstbit=SPI.MSB, ti=False, crc=None)
self.nss = Pin('X5', Pin.OUT_PP)

Then the low level transfers are ...

def writeInstruction(self,command):
buf = bytearray(2)
buf[0]=(command&255)>>2
buf[1]=(command&3)<<6
# assert chip select
self.nss.value(0)
self.spi.send(buf)
# de-assert chip select
self.nss.value(1)

def writeData(self,data):
buf = bytearray(2)
#buf[0] is the MSB
buf[0]=1<<7|(data&255)>>2
buf[1]=(data&3)<<6
# assert chip select
self.nss.value(0)
self.spi.send(buf)
# de-assert chip select
self.nss.value(1)

So, in the end I answered the question I was going to post on the forum. I'm posting this in the hope that some other poor soul will find it one day and save themselves a day of frustration. Best of luck.

Post Reply