SPI Transaction Length Configurability
SPI Transaction Length Configurability
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?
Say I wanted to send a frame of 9 or 13 bits, is it possible to do that in upy with the pyboard?
Re: SPI Transaction Length Configurability
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.
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.
Re: SPI Transaction Length Configurability
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
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
Re: SPI Transaction Length Configurability
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
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
Re: SPI Transaction Length Configurability
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?
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)
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: SPI Transaction Length Configurability
Unless I'm missing something
is loading r3 with a 32 bit word from undefined memory, past the end of the bytearray.
Code: Select all
ldr(r3, [r0, 4])
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: SPI Transaction Length Configurability
Typo. But here is what I ended up with and it actually works up to 12MHz if you comment out the delay block:
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
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))
Thanks,
Steve
Re: SPI Transaction Length Configurability
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
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
Re: SPI Transaction Length Configurability
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.
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.