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

add bitbang spi function - enhancement #33

Open
nerdralph opened this issue Apr 1, 2015 · 19 comments
Open

add bitbang spi function - enhancement #33

nerdralph opened this issue Apr 1, 2015 · 19 comments
Labels
component: libraries type: enhancement waiting for feedback Waiting on additional info. If it's not received, the issue may be closed.

Comments

@nerdralph
Copy link

nerdralph commented Apr 1, 2015

I was measured 4.81mbps for the following code:
void shiftOutFast(byte data)
{
byte i = 8;
do{
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << CLOCK);
if(data & 0x80)
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << DATA);
else
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << DATA);
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << CLOCK);
data <<= 1;
}while(--i);
return;
}

http://nerdralph.blogspot.ca/2015/04/a-4mbps-shiftout-for-esp8266arduino.html

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

@nerdralph
Copy link
Author

I'm slowly starting to figure out the xtensa lx106 instruction set, and have the shiftOut up from 3.81mbps to 6.15mbps. Here's the updated code:
void shiftOutFast(byte data)
{
uint32_t gpio_bits = GPIO_REG_READ(GPIO_OUT_ADDRESS) &
~( (1<< CLOCK) | (1<<DATA) );
byte i = 8;
do{
if(data & 0x80)
gpio_bits |= (1 << DATA);
else
gpio_bits &= ~(1 << DATA);
GPIO_REG_WRITE(GPIO_OUT_ADDRESS, gpio_bits); // clk low & data
data <<= 1;
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << CLOCK);
}while(--i);
return;
}
And here's the disassembly, with -mno-serialize-volatile added to the compiler flags (data passed in a2, loop index i in a4):
0000007c <_Z12shiftOutFasth>:
7c: fffe51 l32r a5, 74 <_Z8shiftOuthhh+0x68>
7f: a37c movi.n a3, -6
81: 0548 l32i.n a4, a5, 0
83: fffd61 l32r a6, 78 <_Z8shiftOuthhh+0x6c>
86: 103430 and a3, a4, a3
89: 742020 extui a2, a2, 0, 8
8c: 840c movi.n a4, 8
8e: ea7c movi.n a10, -2
90: 1b0c movi.n a11, 1
92: 470c movi.n a7, 4
94: 019280 slli a9, a2, 24
97: 2083b0 or a8, a3, a11
9a: 1033a0 and a3, a3, a10
9d: a33890 movltz a3, a8, a9
a0: 0539 s32i.n a3, a5, 0
a2: 440b addi.n a4, a4, -1
a4: 1122f0 slli a2, a2, 1
a7: 0679 s32i.n a7, a6, 0
a9: 744040 extui a4, a4, 0, 8
ac: 742020 extui a2, a2, 0, 8
af: fe1456 bnez a4, 94 <_Z12shiftOutFasth+0x18>
b2: f00d ret.n

The -mno-serialize-volatile, IMHO, should be safe as long no code does a read of an address immediately after a store to the same address (something tha seems to be rather pointless to me).
https://gcc.gnu.org/onlinedocs/gcc-3.1/gcc/Xtensa-Options.html

@igrr
Copy link
Member

igrr commented Apr 2, 2015

I would prefer inline assembley here instead of using -mno-serialize-volatile globally.
Could you try adding this code into another SPIImpl, like it is done with HSPI?
https://github.com/esp8266/Arduino/blob/esp8266/hardware/esp8266com/esp8266/libraries/SPI/include/HSPI.h

Then we could let users create instances of SPIClass(int miso, int mosi, int sck, int cs) for bitbanging SPI or use extern SPIClass SPI for the hardware one.

@nerdralph
Copy link
Author

I haven't figured out lx106 assembler well enough yet.
I'll write a SPI class, or better yet maybe a SPI class template.
On Apr 2, 2015 7:13 AM, "Ivan Grokhotkov" notifications@github.com wrote:

I would prefer inline assembley here instead of using
-mno-serialize-volatile globally.
Could you try adding this code into another SPIImpl, like it is done with
HSPI?

https://github.com/esp8266/Arduino/blob/esp8266/hardware/esp8266com/esp8266/libraries/SPI/include/HSPI.h

Then we could let users create instances of SPIClass(int miso, int mosi,
int sck, int cs) for bitbanging SPI or use extern SPIClass SPI for the
hardware one.

Reply to this email directly or view it on GitHub
#33 (comment).

@nerdralph
Copy link
Author

Looking at the SPI code it, it seems you started with the 1.6 Arduino core
and then cut out the SPI transactions to go with the old klunky API. The
new API with spisettings is cleaner.
On Apr 2, 2015 7:13 AM, "Ivan Grokhotkov" notifications@github.com wrote:

I would prefer inline assembley here instead of using
-mno-serialize-volatile globally.
Could you try adding this code into another SPIImpl, like it is done with
HSPI?

https://github.com/esp8266/Arduino/blob/esp8266/hardware/esp8266com/esp8266/libraries/SPI/include/HSPI.h

Then we could let users create instances of SPIClass(int miso, int mosi,
int sck, int cs) for bitbanging SPI or use extern SPIClass SPI for the
hardware one.

Reply to this email directly or view it on GitHub
#33 (comment).

@igrr
Copy link
Member

igrr commented Apr 3, 2015

Oh right, I wanted to start with something simple and then forgot to add transactions back. Needs to be fixed. HSPI does have beginTransaction, by the way, it's just declared in the SPIImpl interface.

@nerdralph
Copy link
Author

I don't understand why every method of SPIimpl is pure virtual. I think at least end() and setDataMode() should have a default empty implementation, and probably more of the methods.

@Potato-Matic
Copy link

I just wanna say thanks for looking into this issue, and I hope it becomes a thing! It would really help on some of the more pin-restricted variants of the ESP8266 breakout boards, considering all the pins that all need to be present to use hardware SPI.

Though I do wonder about how this would work if a piece of code is expecting and tries to use the hardware SPI port.....I suppose it wouldn't be too hard to patch the begin(); to something like begin(pin1, pin2, pin3, pin4); in a given library as needed. (not a lot of them are written to support passing of an SPI object, hardware or software)

If you guys have a more clever solution, I'd be interested to know!

@me-no-dev
Copy link
Collaborator

here is some AVR code that I have written a while back. It will be fairly easy to add transactions and convert it to ESP. Is this what you are looking for?

SSPI::SSPI(uint8_t mosi, uint8_t miso, uint8_t sck, uint8_t ss){
    _mode = SSPI_MODE0;
    _bit_order = SSPI_MSB_FIRST;
    _bitcount = 8;
    _ss_inverted = false;

    _mosi_pin = portInputRegister(digitalPinToPort(mosi));
    _mosi_ddr = portModeRegister(digitalPinToPort(mosi));
    _mosi_port = portOutputRegister(digitalPinToPort(mosi));
    _mosi_mask = digitalPinToBitMask(mosi);
    _miso_pin = portInputRegister(digitalPinToPort(miso));
    _miso_ddr = portModeRegister(digitalPinToPort(miso));
    _miso_port = portOutputRegister(digitalPinToPort(miso));
    _miso_mask = digitalPinToBitMask(miso);
    _sck_pin = portInputRegister(digitalPinToPort(sck));
    _sck_ddr = portModeRegister(digitalPinToPort(sck));
    _sck_port = portOutputRegister(digitalPinToPort(sck));
    _sck_mask = digitalPinToBitMask(sck);
    _ss_pin = portInputRegister(digitalPinToPort(ss));
    _ss_ddr = portModeRegister(digitalPinToPort(ss));
    _ss_port = portOutputRegister(digitalPinToPort(ss));
    _ss_mask = digitalPinToBitMask(ss);

    *_mosi_ddr |= _mosi_mask;//output
    *_miso_ddr &= ~_miso_mask;//input
    *_sck_ddr |= _sck_mask;//output
    *_ss_ddr |= _ss_mask;//output

    *_mosi_port &= ~_mosi_mask;//low
    *_sck_port &= ~_sck_mask;//low
    *_ss_port |= _ss_mask;//high

}

uint32_t SSPI::transmit(uint32_t data, uint8_t bitcount, bool wait){
    uint8_t index = 0;
    uint8_t i = bitcount;
    uint32_t value = 0;

    //for each bit
    while(i--){
    if (_bit_order == SSPI_MSB_FIRST){
        index = i;
        } else {
            index = ((bitcount - 1) - i);
        }

        //mode 0 and 3 slave reads on leading edge so we need to set the bit before clocking in
        if((_mode & 1) == 0){
            if ((data & (1 << index)) != 0){
                *_mosi_port |= _mosi_mask;//high
            } else {
                *_mosi_port &= ~_mosi_mask;//low
            }
        }

        //clock leading edge
        if((_mode & 2) == 0){
            *_sck_port |= _sck_mask;//high
        } else {
            *_sck_port &= ~_sck_mask;//low
        }

        //modes 0 and 3 read the bit here while 2 and 4 set it to be read on trailing clock edge
        if((_mode & 1) == 0){
            value |= (((*_miso_pin & _miso_mask) != 0) << index);
        } else {
            if ((data & (1 << index)) != 0){
                *_mosi_port |= _mosi_mask;//high
            } else {
                *_mosi_port &= ~_mosi_mask;//low
            }
        }

        //trailing edge
        if((_mode & 2) == 0){
            *_sck_port &= ~_sck_mask;//low
        } else {
            *_sck_port |= _sck_mask;//high
        }

        //modes 2 and 4 read the bit here
        if((_mode & 1) != 0) if((*_miso_pin & _miso_mask) != 0) value |= (1 << index);
    }

    //if requested, wait for the slave to release the miso line
    while(wait && ((*_miso_pin & _miso_mask) != 0));

    return value;
}



//setters

void SSPI::setSsInverted(bool inverted){
    if(_ss_inverted != inverted) *_ss_port ^= _ss_mask; //toggle on change
    _ss_inverted = inverted;
}

void SSPI::setMode(uint8_t mode){
    uint8_t m = (mode & 0x03);
    if((m & 2) != (_mode & 2)) *_sck_port ^= _sck_mask; //toggle on CPOL change
    _mode = m;
}

void SSPI::setBitOrder(uint8_t bitOrder){
    _bit_order = (bitOrder & 1);
}

void SSPI::setBitCount(uint8_t bitCount){
    _bitcount = ((bitCount - 1) & 0x1F) + 1;//32 max
}

void SSPI::select(){
    _ss_inverted?*_ss_port|=_ss_mask:*_ss_port&=~_ss_mask;
}

void SSPI::deselect(){
    _ss_inverted?*_ss_port&=~_ss_mask:*_ss_port|=_ss_mask;
}

@Potato-Matic
Copy link

There were a couple of possibly useful files in my recent duplicate post. Here they are, moved over, just for reference:

https://github.com/greiman/DigitalIO/blob/master/DigitalIO/src/SoftSPI.h
https://github.com/niteris/ArduinoSoftSpi/blob/master/softspi.h

I'm tempted to try replacing the 'fast' digital I/O functions with regular ones and just giving it a shot to see what happens.

@Potato-Matic
Copy link

@me-no-dev
Copy link
Collaborator

4 mbps all good but that is just one type of SPI. If you want to support more modes, time will go up.
Also if you want to support speeds as not all slaves can take 4 mbps or more, that will add to the execution time as well. The pasted above code covers all but speed settings and you can see how long the transmit/transfer function gets.
Overall we can not publish a soft SPI lib that covers only MODE0, 8 bit, as-fast-as-it-can-go logic to the core.

@me-no-dev
Copy link
Collaborator

@igrr, I can wrap the above code to a more ESP8266 approach if you want me to

@pasko-zh
Copy link

Just as a little help for as-fast-as possible bitbang:

You might have a look at my i2c library written in (inline) assembly for the esp8266, called brzo_i2c., section Memory mapped I/O, or directly in the code. The assembly part is rather good documented, so it might help in this context here...

@Potato-Matic
Copy link

Potato-Matic commented Aug 18, 2016

That might help a bunch, considering there are ready-made soft SPI libraries out there written in AVR assembly. It may be as simple as swapping the reading and writing code to acheive a workable result. (one of the repos I linked above depends on "fast" digitalRead() and digitalWrite() functions but is otherwise fairly independent of hardware)

Edit: I don't suppose you know of a decent primer on eps8266 assembly?

@pasko-zh
Copy link

I don't know such an exp8266 assembly primer :-/ However, there are a couple of general inline assembly how-to on the net.
It might sound a bit weird, but it is not that complicated--you just need a lot of nerves :-)
Also, if you are looking at my code, you will see: You won't use so many different assembly commands.
Concerning the Xtensa documentation, you have the two PDFs available here.

And last but not least: Do not hesitate to ask me, I will always try to help!

@devyte
Copy link
Collaborator

devyte commented Oct 20, 2017

@nerdralph @Matthew-Bradley @me-no-dev
what is the status of this? Did ayone reach a workable solution?

@devyte devyte added the waiting for feedback Waiting on additional info. If it's not received, the issue may be closed. label Oct 20, 2017
@Potato-Matic
Copy link

I've come to think we're overthinking this problem. For most applications, a fast spi interface isn't really needed. A lot of the devices that get attached to an spi buss are pretty low-speed, like RFID card readers and temperature sensors. This is especially true if you're the master device and have control of the clock.

I remember seeing a software spi implementation for the AVR that relied upon functions like digitalwritefast(). I'm thinking that if you went and replaced all such functions with the regular versions, it would be perfectly adequate, even given the ESP's really slow GPIO pins. (compared to an AVR)

@nerdralph
Copy link
Author

nerdralph commented Oct 23, 2017 via email

@tablatronix
Copy link
Contributor

What ever happend with this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: libraries type: enhancement waiting for feedback Waiting on additional info. If it's not received, the issue may be closed.
Projects
None yet
Development

No branches or pull requests

7 participants