What is the difference between com.pi4j.io.spi.SpiDevice and "manual" SPI? (ILI9341 LCD Display)

434 views
Skip to first unread message

Stefan Schildbach

unread,
Mar 19, 2016, 12:51:38 PM3/19/16
to Pi4J
Hey folks,

I'm currently stuck at implementing an interface for an LCD Display (ILI9341 controller). I could track down my problem to the SPI interface.
Can someone explain the difference between the following two approaches and may clarify what I am missing?

Version 1 with SpiDevice (spi) not working:
private void write(byte data) throws IOException
{
channelSelect.low();
spi.write(data);
channelSelect.high();
}

Version 2 with manual SPI working:
private void write(byte data) throws IOException
{
channelSelect.low();

// MSB first manual data transmission
for (int i = 0; i < 8; i++)
{
mosi.setState((data & 0x80) != 0);
data = (byte) ((data << 1) & 0xFF);
clock.low();
// NOP
clock.high();
}
channelSelect.high();
}

Version 2 works (very slow), but I can't get version 1 to do the same. I would have guessed the two snippets should both get the job done - but obviously they don't ;)
I would be very thankful for any hints and pieces of advice.

Have a nice day,
Stefan

Robert Savage

unread,
Mar 19, 2016, 1:30:59 PM3/19/16
to Pi4J
Hi Stephan,

Use SpiFactory to get your SpiDevice instance:

Pi4J uses the spidev linux driver under the hood and it manages the CS state for you based on the chip select you used when creating your SpiDevice with SpiFactory.  
So you don't have to manage the CS pin state in your code at all.

Thanks, Robert 

Robert Savage

unread,
Mar 19, 2016, 1:38:05 PM3/19/16
to Pi4J
Your second approach is "bit-banging" where you are managing your own code loop to transmit each bit.  The SpiDevice approach pushes the supplied data buffer to the kernel driver and lets the kernel driver do the work of transmitting each bit in sequence much closer to the metal and thus much faster.  

The second approach is probably excessively slow because for each GPIO pin state change you make (for each bit in the data buffer), it has to perform this path to drive the hardware:  
Your-Java-Code > Pi4J > JNI > WiringPi > GpioMem > Hardware.  

As you can see, this is not optimal for bit-banging in an efficient manner.

Thanks, Robert


On Saturday, March 19, 2016 at 12:51:38 PM UTC-4, Stefan Schildbach wrote:

Stefan Schildbach

unread,
Mar 19, 2016, 2:33:52 PM3/19/16
to Pi4J
Hey Robert,

thank you for your fast response and nice explanation of my slow "bit-banging approach" cause.

So you don't have to manage the CS pin state in your code at all.
 
Unfortunately, as far as I understood it right, the driver needs active modification of the CS (Datasheet page 35) to work:


Is there a way to map the bit-banging (version 2) to a solid version with the SpiDevice or with something else - simply more effective?

Version 3: Removing the channelSelect operations from version 1 does not show any improvements.
private void write(byte data) throws IOException
{
spi.write(data);
}

Tackling the problem from another perspective - is the usage of SPI necessary here or is the pin labeling just confusing and something else is more appropriate:
- SDI (MOSI)
- SDO (MOSI)
- SCK
- CS

(Image source: ElecFreaks - Wiki)

Thanks for your time and effort. I'm still open for further advice :)
Stefan

Robert Savage

unread,
Mar 19, 2016, 2:47:02 PM3/19/16
to Pi4J
I'm no expert, but it looks like SPI communication to me.  

What data bytes are you trying to send?  I have a protocol analyzer I can use to capture the state of all the SPI pins.  I can simply send the bytes over SPI can capture the data and pins states with the analyzer.  Maybe that can shed some light on what's going wrong.   

I know that the SPI driver can be configured with different "modes".  Maybe one of these alternate modes are needed:

Thanks, Robert

Robert Savage

unread,
Mar 19, 2016, 3:06:14 PM3/19/16
to pi...@googlegroups.com
Here is an example of sending "Hello World" ASCII bytes via SPI.
(Channel 3 is the CS signal)



Here is the code that is generating this:

import com.pi4j.io.spi.SpiChannel;
import com.pi4j.io.spi.SpiDevice;
import com.pi4j.io.spi.SpiFactory;

import java.io.IOException;

public class SpiExample {

    // SPI device
    public static SpiDevice spi = null;

    public static void main(String args[]) throws InterruptedException, IOException {

        System.out.println("<--Pi4J--> SPI test program");

        spi = SpiFactory.getInstance(SpiChannel.CS0,
                                     SpiDevice.DEFAULT_SPI_SPEED, // default spi speed 1 MHz
                                     SpiDevice.DEFAULT_SPI_MODE); // default spi mode 0


        byte[] result = spi.write("Hello World".getBytes("US-ASCII"));
    }
}

Robert Savage

unread,
Mar 19, 2016, 3:22:38 PM3/19/16
to pi...@googlegroups.com
If you need the CS pin to toggle states in between each byte, you can do this instead:

import com.pi4j.io.spi.SpiChannel;
import com.pi4j.io.spi.SpiDevice;
import com.pi4j.io.spi.SpiFactory;

import java.io.IOException;

public class SpiExample {

// SPI device
public static SpiDevice spi = null;

public static void main(String args[]) throws InterruptedException, IOException {

System.out.println("<--Pi4J--> SPI test program");

spi = SpiFactory.getInstance(SpiChannel.CS0,
SpiDevice.DEFAULT_SPI_SPEED, // default spi speed 1 MHz
SpiDevice.DEFAULT_SPI_MODE); // default spi mode 0

        // create SPI payload
byte[] buffer = "Hello World".getBytes("US-ASCII");

// send one byte at a time
for(byte b : buffer){
spi.write(b);
}
}
}


This produces the following result (screenshot):


If this works, but is still not fast enough, then I could create more optimized SPI method to handle splitting up the byte buffer/array in the C native code in Pi4J.

Thanks, Robert


Robert Savage

unread,
Mar 19, 2016, 3:46:09 PM3/19/16
to Pi4J
I was also looking at an Adafruit example implementation in Python for the ILI9341:

They are using SPI but it looks like you have to manually handle the "D/Cx" GPIO pin to distinguish between DATA vs COMMAND payloads.

# Set DC low for command, high for data self._gpio.output(self._dc, is_data)

Also note the SPI is using mode 0 and the speed is much higher than the default 1MHz that Pi4J uses by default:

spi.set_mode(0)
spi.set_bit_order(SPI.MSBFIRST)
spi.set_clock_hz(64000000)

Thanks, Robert





Stefan Schildbach

unread,
Mar 20, 2016, 6:01:54 AM3/20/16
to Pi4J
Hey Robert,

this is some piece of really cool hard-/software you got there.
I prepared a minimum working and non-working example - may I ask you to analyze them?

It seems to me, that frome your findings, the 
spi.write((byte) data);
should do exactly what I need - but the display proofs me wrong.

I was also looking at an Adafruit example implementation in Python for the ILI9341:

Thanks for the link, I'm going to try their script and compare it with the other ones I found on my way.
The clock speed could be a problem, but on the other hand bit-banging gets the job done as well.

Thanks in advance, have a nice beginning of spring :)
Stefan

Am Samstag, 19. März 2016 17:51:38 UTC+1 schrieb Stefan Schildbach:
MWE.java
MNWE.java

Robert Savage

unread,
Mar 20, 2016, 7:12:10 AM3/20/16
to Pi4J
Hi Stefan,

The first thing I noticed is that while running MWE, the clock looks to be inversed.  It starts HI and then signals LOW for each of the 8 bits.   You can see in the screenshot that the SPI decoder is complaining about it.  This is legal for alternate SPI modes, but not mode 0.
The screenshot is capturing the very first data packet written out.
  • CH 3 = Chip Select
  • CH 5 = Data/Command
  • CH 6 = Reset


Robert Savage

unread,
Mar 20, 2016, 7:30:27 AM3/20/16
to pi...@googlegroups.com
And here is the first data packet using MNWE



One thing that I ran into is that I was not able to successfully run and capture SPI data when running MNWE immediately after MWE.  
This is  because the GPIO pins used by SPI were provisioned as regular GPIO pins in MWN and they were left is some state that was not working with the SPI device driver.
I simply rebooted the RPi and then ran MNWE just fine.

Note, you can use the WiringPi utility to see that state of all pins if you want to further see the pins states:  "gpio readall"

Thanks, Robert

Robert Savage

unread,
Mar 20, 2016, 8:16:52 AM3/20/16
to Pi4J
Also don't instantiate/provision the CS pin , we want to allow the SPI dev driver to control this pin.

// Init Pins and SPI
dataCommandSelect = GpioFactory.getInstance().provisionDigitalOutputPin(RaspiPin.GPIO_05); // 5) D/C
reset = GpioFactory.getInstance().provisionDigitalOutputPin(RaspiPin.GPIO_06, PinState.HIGH); // 6) RESET
//channelSelect = GpioFactory.getInstance().provisionDigitalOutputPin(RaspiPin.GPIO_10, PinState.HIGH); // 7) CS
spi = SpiFactory.getInstance(SpiChannel.CS0);


Thanks, Robert

On Sunday, March 20, 2016 at 7:30:27 AM UTC-4, Robert Savage wrote:
And here is the first data packet using MNWE



One thing that I ran into is that I was not able to successfully run and capture SPI data when running MNWE immediately after MWE.  
This is  because the GPIO pins used by SPI were provisioned as regular GPIO pins in MWN and they were left is some state that was not working with the SDI device driver.

Stefan Schildbach

unread,
Mar 21, 2016, 12:31:03 PM3/21/16
to Pi4J
Hey Robert,

based on your findings I would assume that I need to run SPI Mode 2 or 3 to simply inverse the clock polarity.
Nevertheless, none of them works either :'(

As you have a spare minute, would you run them with you analyzer to see what happens there?

Thank you for helping me out!
Stefan

Am Samstag, 19. März 2016 17:51:38 UTC+1 schrieb Stefan Schildbach:
MNWE_Mode2.java
MNWE_Mode3.java

Robert Savage

unread,
Mar 21, 2016, 12:45:00 PM3/21/16
to pi...@googlegroups.com
Did you try the rebooting and excluding CS pin provisioning in your code that I wrote about?  That definitely presented a problem for me until I made those changes?
And only run the MNWE.  The simple matter of running the MWE would screw up the pins for the Spi device driver.

Thanks, Robert

Stefan Schildbach

unread,
Mar 21, 2016, 12:58:50 PM3/21/16
to Pi4J
Hey Robert,

I removed the provisioning, inserted an unexport (just to be sure) and rebooted - and it finally works!
The almighty "Have you turned it off and on again?" together with the provisioning was the key :)

Thank you very much for all your time and effort with me and of course especially with the Pi4J project!
Stefan

Am Samstag, 19. März 2016 17:51:38 UTC+1 schrieb Stefan Schildbach:

Robert Savage

unread,
Mar 21, 2016, 1:14:22 PM3/21/16
to Pi4J
No problem at all.  Glad to hear you got it working.  Maybe in the future some logical pin check inside Pi4J could be performed if attempting to use the SPI APIs when the pins are in the exported states.  First time I have run into this issue :-) 

Thanks, Robert

Emmanuel Niati

unread,
Oct 26, 2020, 3:20:12 PM10/26/20
to Pi4J
Hey everybody,

Happy to be with you !

Is there an ultimate soluce witch works ?

I tried some classes from these exchanges but none is working...

Please help me !!!

THX,

Emmanuel Niati

unread,
Oct 28, 2020, 9:41:35 AM10/28/20
to Pi4J
Happy to have rejoined pi4j !

Is there an ultimate soluce witch works ?

I tried some classes from these exchanges but none is working...

Please help me !!!

THX,

Reply all
Reply to author
Forward
0 new messages