NRF24L01+ and 16x2LCD, display showing weird characters.

So I have a 16x2 LCD connected to my UNO with an NRF24L01, they share SPI pins. Both work using standalone sketches, the radio is working really well, as is the LCD. I am using the NRF24 library and the liquidcrystsal library.

My issue comes when I try to use the two in conjunction, I can write messages to the screen, but as soon as radio.begin(); is used, the 16x2 goes mad and I get a “D” on the screen instead of my message.
Here is my full code, it is an adaptation of pingpair_dyn from NRF24.

/*


/**
 * Example using Dynamic Payloads 
 *
 * This is an example of how to use payloads of a varying (dynamic) size. 
 */
#include <LiquidCrystal.h>
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"

//
// Hardware configuration
//

// Set up nRF24L01 radio on SPI bus plus pins 7 & 8
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
RF24 radio(7,8);

// sets the role of this unit in hardware.  Connect to GND to be the 'pong' receiver
// Leave open to be the 'ping' transmitter
const int role_pin = 6;

//
// Topology
//

// Radio pipe addresses for the 2 nodes to communicate.
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

//
// Role management
//
// Set up role.  This sketch uses the same software for all the nodes
// in this system.  Doing so greatly simplifies testing.  The hardware itself specifies
// which node it is.
//
// This is done through the role_pin
//

// The various roles supported by this sketch
typedef enum { role_ping_out = 1, role_pong_back } role_e;

// The debug-friendly names of those roles
const char* role_friendly_name[] = { "invalid", "Ping out", "Pong back"};

// The role of the current running sketch
role_e role;

//
// Payload
//

const int min_payload_size = 4;
const int max_payload_size = 32;
const int payload_size_increments_by = 1;
int next_payload_size = min_payload_size;

char receive_payload[max_payload_size+1]; // +1 to allow room for a terminating NULL char

void setup(void)
{
  //
  // Role
  //
  lcd.begin(16, 2);
  lcd.clear();
  lcd.print("Hello WOrld");
  delay(400);
  role = role_ping_out;

  //
  // Print preamble
  //

  Serial.begin(9600);
  
  Serial.println(F("RF24/examples/pingpair_dyn/"));
  Serial.print(F("ROLE: "));
  Serial.println(role_friendly_name[role]);
  delay(400);
  lcd.clear();
  delay(400);
  lcd.print("Hello");
  //
  // Setup and configure rf radio
  //
  delay(400);
  radio.begin();

  // enable dynamic payloads
  radio.enableDynamicPayloads();

  // optionally, increase the delay between retries & # of retries
  radio.setRetries(5,15);

  //
  // Open pipes to other nodes for communication
  //

  // This simple sketch opens two pipes for these two nodes to communicate
  // back and forth.
  // Open 'our' pipe for writing
  // Open the 'other' pipe for reading, in position #1 (we can have up to 5 pipes open for reading)

  if ( role == role_ping_out )
  {
    radio.openWritingPipe(pipes[0]);
    radio.openReadingPipe(1,pipes[1]);
    lcd.print("Ping Out");
  }
  else
  {
    radio.openWritingPipe(pipes[1]);
    radio.openReadingPipe(1,pipes[0]);
  }

  //
  // Start listening
  //

  //radio.startListening();

  //
  // Dump the configuration of the rf unit for debugging
  //
  Serial.print("End setup");
  //radio.printDetails();
}

void loop(void)
{
  //
  // Ping out role.  Repeatedly send the current time
  //

  if (role == role_ping_out)
  {
    // The payload will always be the same, what will change is how much of it we send.
    static char send_payload[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ789012";

    // First, stop listening so we can talk.
    radio.stopListening();

    // Take the time, and send it.  This will block until complete
    Serial.print(F("Now sending length "));
    Serial.println(next_payload_size);
    radio.write( send_payload, next_payload_size );

    // Now, continue listening
    radio.startListening();

    // Wait here until we get a response, or timeout
    unsigned long started_waiting_at = millis();
    bool timeout = false;
    while ( ! radio.available() && ! timeout )
      if (millis() - started_waiting_at > 500 )
        timeout = true;

    // Describe the results
    if ( timeout )
    {
      Serial.println(F("Failed, response timed out."));
    }
    else
    {
      // Grab the response, compare, and send to debugging spew
      uint8_t len = radio.getDynamicPayloadSize();
      
      // If a corrupt dynamic payload is received, it will be flushed
      if(!len){
        return; 
      }
      
      radio.read( receive_payload, len );

      // Put a zero at the end for easy printing
      receive_payload[len] = 0;

      // Spew it
      Serial.print(F("Got response size="));
      Serial.print(len);
      Serial.print(F(" value="));
      Serial.println(receive_payload);
    }
    
    // Update size for next time.
    next_payload_size += payload_size_increments_by;
    if ( next_payload_size > max_payload_size )
      next_payload_size = min_payload_size;

    // Try again 1s later
    delay(100);
  }

  //
  // Pong back role.  Receive each packet, dump it out, and send it back
  //

  if ( role == role_pong_back )
  {
    // if there is data ready
    while ( radio.available() )
    {

      // Fetch the payload, and see if this was the last one.
      uint8_t len = radio.getDynamicPayloadSize();
      
      // If a corrupt dynamic payload is received, it will be flushed
      if(!len){
        continue; 
      }
      
      radio.read( receive_payload, len );

      // Put a zero at the end for easy printing
      receive_payload[len] = 0;

      // Spew it
      Serial.print(F("Got response size="));
      Serial.print(len);
      Serial.print(F(" value="));
      Serial.println(receive_payload);

      // First, stop listening so we can talk
      radio.stopListening();

      // Send the final one back.
      radio.write( receive_payload, len );
      Serial.println(F("Sent response."));

      // Now, resume listening so we catch the next packets.
      radio.startListening();
    }
  }
}

I have a 16x2 LCD connected to my UNO with an NRF24L01, they share SPI pins

Do they share the same Slave Select pin by any chance ?

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

That is the constructor for a 4 bit parallel interface LCD, not an SPI LCD. I am not aware of any version of the LiquidCrysral library that uses SPI. The radio and the LCD cannot share pins. Use different pins for rs and en as those pins are used for the SPI for the radio, as are 7 and 8.

If your version of the LiquidCrystal library can use SPI, please provide a link to the library.

It must be some parallel universe where it makes sense for d4 = 5 and d5 = 4

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;

...R

I copied the code from the example programs for both libraries for speed, ( I am an experienced raspberry pi user, this is my first Arduino project), so I thought the LCD was using the SPI bus pins. So I can just use the LCD on any pins I want? (not SPI bus though)

So I can just use the LCD on any pins I want?

Yes. And remember that the analog input pins can be used as digital pins, too (except A6 and A7 on Nano).

Robin, if it helps to make more sense, D4 - D7 are the names of the (labeled) LCD data pins.

Ok, all of the code is identical except I switched RS as pin 0 and en as 9. Instead of clearing the display, I get double lines in between words, I'm guessing its something to do with pin 0 being an RX pin, how do I use an analog in as a digital out?

update: I think i've cracked it, thanks for your help.

To set A0 to output use

pinMode(A0, OUTPUT);
// or
pinMode(14, OUTPUT);

You can use the Ax notation or numeric. I use the numeric pin numbers for the analog pins when used as digital pins (A0=14, A1=15, A2=16, etc.). The numeric names seem help me to remember that I am using them as digital pins. It is a matter of personal choice, though.

Rev_02:
update: I think i've cracked it, thanks for your help.

Perhaps take a look at using I2C , less pins etc.

Using other lcd i2c libraries may be even simpler.