Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend SPI module API with improved transaction handling #693

Merged
merged 9 commits into from Oct 21, 2015

Conversation

Projects
None yet
2 participants
@devsaurus
Copy link
Member

devsaurus commented Oct 18, 2015

The current SPI module enforces artificial restrictions that can be removed now with the information published at
http://d.av.id.au/blog/hardware-spi-hspi-command-data-registers/

While #674 enabled arbitrary clock frequencies, this PR targets variable bit length and full support of the SPI hardware features found on the ESP8266 chip:

  • databit parameter in spi.setup() is now used when executing transactions with spi.send() and spi.recv() - it was ignored up to now and databit length was forced to 8 bit.
  • spi.recv() should finally gather data from the slave - according to the reverse engineering results, this was not possible at all with the old driver code.
  • New command spi.transaction(id, cmd_bitlen, cmd_data, addr_bitlen, addr_data, mosi_bitlen, dummy_bitlen, miso_bitlen) to fully exploit the hardware features and enable the user to craft advanced SPI transactions:
    • cmd_bitlen - bit length of the command phase
    • cmd_data - data for command phase
    • addr_bitlen - bit length for address phase
    • addr_data - data for command phase
    • mosi_bitlen - bit length of the MOSI phase
    • dummy_bitlen - bit length of the dummy phase
    • miso_bitlen - bit length of the MISO phase
  • New command spi.set_mosi(id, offset, bitlen, data1, [data2], ..., [datan]) to fill MOSI buffer.
    • offset - bit offset into MOSI buffer for inserting data1 and subsequent items
    • bitlen - bit length of data1, data2, ...
  • New command spi.get_miso(id, offset, bitlen, num) to extract data items from the MISO buffer.
    • offset - bit offset into MISO buffer for first data item
    • bitlen - bit length of data items
    • num - number of data items to retrieve

@devsaurus devsaurus self-assigned this Oct 18, 2015

@devsaurus devsaurus referenced this pull request Oct 18, 2015

Closed

SPI support? #50

make spi.send() transparent for 32 bit data
accept negative values as u32

devsaurus added a commit that referenced this pull request Oct 21, 2015

Merge pull request #693 from devsaurus/dev-hspi_apiext
Extend SPI module API with improved transaction handling

@devsaurus devsaurus merged commit c223ecf into nodemcu:dev Oct 21, 2015

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details

@devsaurus devsaurus deleted the devsaurus:dev-hspi_apiext branch Oct 21, 2015

@jfollas

This comment has been minimized.

Copy link
Contributor

jfollas commented on 1909271 Oct 22, 2015

Curious about the removal of platform_spi_send_recv. While NodeMCU's SPI API is often depicted as synchronous (you write bytes, then read bytes separately), SPI itself is asynchronous (incoming bits are clocked in at the same time as outgoing bits), and that's the protocol used on the wire by some devices.

platform_spi_send_recv permitted this reading from MISO at the same time that it was writing to MOSI because the return value of the funciton was the byte read in. I don't yet see how this is performed with the new API.

This comment has been minimized.

Copy link
Member Author

devsaurus replied Oct 22, 2015

Good catch, and I agree with your findings.
From the available documentation, I couldn't find a way how to support the receive-while-sending case. It seems like a restriction of the hardware which enforces the separation (time-wise) of sending and receiving. On top, the Lua API wasn't exposing this aspect of platform_spi_send_recv as well. So I decided to align the platform layer with the known functionality of the hardware.
Do you have positive indication that the previous function actually fulfilled this purpose? I would be very interested to update the SPI module accordingly. But I'd require better HW documentation.
A software based SPI implementation could help here for sure...

@jfollas

This comment has been minimized.

Copy link
Contributor

jfollas commented on 1909271 Oct 22, 2015

Curious about the removal of platform_spi_send_recv. While NodeMCU's SPI API is often depicted as synchronous (you write bytes, then read bytes separately), SPI itself is asynchronous (incoming bits are clocked in at the same time as outgoing bits), and that's the protocol used on the wire by some devices.

platform_spi_send_recv permitted this reading from MISO at the same time that it was writing to MOSI because the return value of the funciton was the byte read in. I don't yet see how this is performed with the new API.

This comment has been minimized.

Copy link
Member Author

devsaurus replied Oct 22, 2015

Good catch, and I agree with your findings.
From the available documentation, I couldn't find a way how to support the receive-while-sending case. It seems like a restriction of the hardware which enforces the separation (time-wise) of sending and receiving. On top, the Lua API wasn't exposing this aspect of platform_spi_send_recv as well. So I decided to align the platform layer with the known functionality of the hardware.
Do you have positive indication that the previous function actually fulfilled this purpose? I would be very interested to update the SPI module accordingly. But I'd require better HW documentation.
A software based SPI implementation could help here for sure...

@jfollas

This comment has been minimized.

Copy link
Contributor

jfollas commented Oct 22, 2015

Receive while sending is exactly what the Lua .recv() function did - it called platform_spi_send_recv and sent a dummy 0xFF byte over MOSI, and then the MISO byte was returned.

platform_spi_send_recv called spi_mast_byte_write. Looking at that code, only SPI_W0 was used. My assumption was that the ESP outputs each bit to MOSI, then replaces the same bit in W0 with the incoming bit read from MISO. This probably extends to all of the Wn registers for your transaction code purposes.

void spi_mast_byte_write(uint8 spi_no, uint8 *data)
{
    if(spi_no>1)        return; //handle invalid input number

    while(READ_PERI_REG(SPI_CMD(spi_no))&SPI_USR);

    WRITE_PERI_REG(SPI_W0(spi_no), *data);

    SET_PERI_REG_MASK(SPI_CMD(spi_no), SPI_USR);
    while(READ_PERI_REG(SPI_CMD(spi_no))&SPI_USR);

    *data = (uint8)(READ_PERI_REG(SPI_W0(spi_no))&0xff);
}  

In my own codebase, I had patched the Lua SPI module to build up a string one byte at a time with the data returned by platform_spi_send_recv as part of the .send() function, and then return that string as a second return value from the function call (so that it wouldn't break the current API, the first value remained as the # of bytes sent).

Usage from Lua resembled: n, misoData = spi.send(1, 24, 24, 00, 00)

The developer would then need to know how to parse the string of bytes in misoData (i.e., likely that not every byte in the returned string was meaningful, but at least incoming data was not lost in the process while outgoing data was still being sent).

@devsaurus

This comment has been minimized.

Copy link
Member Author

devsaurus commented Oct 22, 2015

Receive while sending is exactly what the Lua .recv() function did - it called platform_spi_send_recv and sent a dummy 0xFF byte over MOSI, and then the MISO byte was returned.

My point was that the user can't control the data that's being "sent" by spi.recv(). This function just provided the received data. It's completely irrelevant that a receive while sending feature is suggested by the underlying platform_spi_send_recv(). The user gets the same functionality from spi.recv() with this PR as before.

platform_spi_send_recv called spi_mast_byte_write. Looking at that code, only SPI_W0 was used. My assumption was that the ESP outputs each bit to MOSI, then replaces the same bit in W0 with the incoming bit read from MISO.

The code suggested this, yes. But this type of full-duplex transaction is not mentioned in Hardware SPI (HSPI) Command & Data Registers:

MISO DATA: During this period, the MOSI line simply outputs 0, while all the data on the MISO line is clocked in to the internal SPI_Wx registers. It is important to note that data in is only collected during this period. Communication is half-duplex.

Maybe I'm just looking at incomplete HW documentation (a common issue with the ESP8266, I guess). Do you know of other references that describe full-duplex transactions?

@jfollas

This comment has been minimized.

Copy link
Contributor

jfollas commented Oct 23, 2015

Few points:

  1. Yes, Espressif's documentation is pretty lacking. I'm not surprised that the SPI interface is documented incorrectly/incompletely. I've been going by trial and error.
  2. My point about .recv() was that it demonstrated that the send-while-receive worked as I expected (so you could get the MISO data from a .send() in the same way that .recv() was... even though this was not implemented publicly yet in NodeMCU for .send()).
  3. Maybe the user should be able to control what is sent during .recv(). I have a device that expects 0xFF as a dummy byte for the purpose of clocking in data when you have nothing else to send. Sending a 0x00 will be interpreted differently. I can see this implemented as x=spi.recv(1,3,255) with the 255 being the [optional] dummy byte. Though, if I had receive during .send(), then I would just send out 0xFF myself.
  4. Your concept of a transaction may actually have everything that I need - I haven't studied it in depth yet (been working on the hardware side for the past few days, and just noticed that my .send() modifications were broken after the latest pull - that's what started this thread).

If you'd like to see an example of the type of SPI protocol that I'm trying to work with, check out page 66 of this datasheet:

http://www.st.com/st-web-ui/static/active/en/resource/technical/document/datasheet/DM00111861.pdf

@devsaurus

This comment has been minimized.

Copy link
Member Author

devsaurus commented Oct 23, 2015

Guess I understand the specific requirements for such use cases better now, thanks a lot for pointing this out.

  • spi.recv() would need an optional parameter for default send data.
  • spi.send() returns the received data in full-duplex configuration.

    Looking at your proposal - why should it also return the number of data items? It'd be always the same as the number of sent items.
  • All based on a full-duplex enabled platform layer for sure.

A respective PR will require more investigation beforehand to understand the full-duplex topic on hardware level, though. I couldn't find any concise information until now.

@jfollas

This comment has been minimized.

Copy link
Contributor

jfollas commented Oct 24, 2015

spi.send() would return the number bytes sent, like it does now, as the first element of the returned Lua tuple so that it doesn't break anybody's existing code. To that, I would add the received bytes string/array/table/whatever as the second element of the returned Lua tuple.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.