LCD SPI passthrough

Hello Community,

I’m officially now at my wits end with this project. If anyone can please help me I would be so grateful as I’m 40+ hours in and at a standstill of what I thought would be the easy part.

Project goal:

Using a Mega2560 to read the SPI communications between my car radio and its LCD Screen powered by a NJU6623 15-CHARACTER 1-LINE DOT MATRIX LCD CONTROLLER DRIVER with OUTPUT PORT

The aim is to scan for the characters Ex: and insert my own string “TEST” for example.

Status:

Bench testing. I have the Mega running the below code connected to the screen and I am able to write to the screen correctly and it displays the text I enter “TEST”.

#include <SPI.h>
#include <Time.h>
#include <Wire.h>

#include "RTClib.h"


RTC_DS3231 rtc;

//PORTB
#define DATAOUT 51 // MOSI
#define SPICLOCK  52 //sck
#define ssPin 53 // PB0

//PORT L
#define mode0 49
#define mode1 48




/**********************************************
// User configurable
**********************************************/
String Oil = "TEST";

void setup() {

  // Control pins for the display SPI
  pinMode(mode0, OUTPUT);
  pinMode(mode1, OUTPUT);
  pinMode(ssPin, OUTPUT);


  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE3)); 
  Serial2.begin (4800, SERIAL_8E1);
  Serial.begin(9600);


}

void loop()
{

populateFixedLcdDisplay(Oil);    
}



void populateFixedLcdDisplay(String _lcdInput)
{
  _lcdInput.remove(12);

  if (_lcdInput.length() < 12)
  {
    for (byte i = _lcdInput.length() + 1; i <= 12; i++)
    {
      _lcdInput.concat(' ');
    }
  }

  byte _start = 0x80;

  PORTB &= ~(1 << PB0);
  SPI.transfer(0x80);
  PORTB |= (1 << PB0);

  for (byte i = 0; i < _lcdInput.length(); i++)
  {
    PORTL |= (1 << PL0);
    PORTB &= ~(1 << PB0);
    SPI.transfer(_lcdInput.charAt(i));
    PORTB |= (1 << PB0);
    delayMicroseconds(42);
    PORTL &= ~(1 << PL0);
  }
}

This code was taken from Tony Chatfield - S1-RX8-AC-Display-controller project. His aim was to entirely replace the radio section of the SPI controls. The Wiki has a lot of detail and compiles 90% of my knowledge on the subject.

I have an Uno set as an SPI slave device to read from the master. I used the tutorial/example from Here to test communications from slave to master. I made slight adjustments to match the SPI mode between slave and master.

Altering the master code void loop to:

//populateFixedLcdDisplay(Oil)
char c;
   digitalWrite(SS, LOW); // enable Slave Select
   // send test string
   for (const char * p = "Hello, World\r" ; c = *p; p++) 
   {
      SPI.transfer (c);
      Serial.print(c);
   }
   digitalWrite(SS, HIGH); // disable Slave Select
   delay(2000);

and using the Slave code on an Uno:

#include <SPI.h>
byte ssPin = 10;
char buff [50];
volatile byte indx;
volatile boolean process;
 
void setup (void) {
   Serial.begin (9600);
      SPI.setDataMode(SPI_MODE3);
   pinMode(MISO, OUTPUT); // have to send on master in so it set as output
   SPCR |= _BV(SPE); // turn on SPI in slave mode
   indx = 0; // buffer empty
   process = false;
   SPI.attachInterrupt(); // turn on interrupt
}
 
ISR (SPI_STC_vect) // SPI interrupt routine 
{ 
   byte c = SPDR; // read byte from SPI Data Register
   if (indx < sizeof buff) {
      buff [indx++] = c; // save data in the next index in the array buff
      if (c == '\r') //check for the end of the word
      process = true;
   }
}
 
void loop (void) {
   if (process) {
      process = false; //reset the process
 
      Serial.println (buff); //print the array on serial monitor
      indx= 0; //reset button to zero
      
   }
}

This gives me valid communication from master to slave. Printing Hello, World in the serial monitor.

If I enable populateFixedLcdDisplay(Oil) in the master code I get the below on repeat in the slave serial monitor.

⸮TEST Hello, World

From this I can see my write to the LCD screen is being picked up by the slave. I don’t understand why the ⸮ is there or the large spacing between TEST and Hello, World.

I understand that the slave code is looking for the last part of the string from the master. In this case \r.

my first question is how do I get it to look for the first character instead of the last and still print he message?

My second question is why if I try to run the MEGA/Master code on an UNO do I get the error PORTL is undefined? Its not defined in either but is fine on the Mega.

I hope from being able to understand the answer to question 1 I can make my own progress and will give the boost needed to continue as I’m on the verge of giving up.

The unwanted ⸮ is probably because the interrupt service routine is being triggered once when the statement :
SPI.attachInterrupt() ; // turn on interrupt
is executed. You may have to clear a pending interrupt.

The large number of unwanted spaces is because the function populateFixedLcdDisplay(Oil) bulks out the phrase “TEST” to 12 characters with trailing spaces.

Your slave code collects a buffer load, then recognizes the \r character, prints and cleans the buffer. How else do you want to do it? If you attempt to print every character as it comes in, the printing may clash with the reading of the characters. You can try it though by setting process to true unconditionally in the interrupt service routine.

It looks like PORTL is for pins 48 and 49 which don’t exist on a Uno.

6v6gt:
The unwanted ⸮ is probably because the interrupt service routine is being triggered once when the statement :
SPI.attachInterrupt() ; // turn on interrupt
is executed. You may have to clear a pending interrupt.

The large number of unwanted spaces is because the function populateFixedLcdDisplay(Oil) bulks out the phrase “TEST” to 12 characters with trailing spaces.

Your slave code collects a buffer load, then recognizes the \r character, prints and cleans the buffer. How else do you want to do it? If you attempt to print every character as it comes in, the printing may clash with the reading of the characters. You can try it though by setting process to true unconditionally in the interrupt service routine.

It looks like PORTL is for pins 48 and 49 which don’t exist on a Uno.

Hello 6v6gt,

Thank you for looking through my post. I’ve been struggling with this much. Its one of those weird things where I think its doable so I cant put it down but its starting to mess my head up a bit.

Is it this section that’s checking for length and padding out?

 i <= 12; i++

I am looking into the attachInterrupt() section/ ISR to understand what its actually doing. I’m starting to get my head around the array and having mixed responses moving it out of being an interrupt but its progress so I’m investigating further.

With PORTL sissue I have changed the pins to match an UNO, setting them to 4 & 5 but it still throws the error. I cant understand why as PORTB isn’t having the same issue it isn’t declared either. I’ve had a few friends look at this too as I was trying to convert the code before buying a MEGA but we are all stumped by it.

#include <SPI.h>
#include <Time.h>
#include <Wire.h>
#include "DS3231.h"
#include "RTClib.h"

RTC_DS3231 rtc;

//PORTB
#define DATAOUT 11 // MOSI
#define SPICLOCK  13 //sck
#define ssPin 10 // PB0

//PORT L
#define mode0 5
#define mode1 4

/**********************************************
// User configurable
**********************************************/
String Oil = "";


void setup() {

  // Control pins for the display SPI
  pinMode(mode0, OUTPUT);
  pinMode(mode1, OUTPUT);
  pinMode(ssPin, OUTPUT);


  // Real time clock
  rtc.begin();


  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE3)); 
  Serial.begin(9600);
}

void loop()
{


char c;
   digitalWrite(SS, LOW); // enable Slave Select
   // send test string
   for (const char * p = "Hello, World\r" ; c = *p; p++) 
   {
      SPI.transfer (c);
      Serial.print(c);
   }
   digitalWrite(SS, HIGH); // disable Slave Select
   delay(2000);

    
}


void populateFixedLcdDisplay(String _lcdInput)
{
  _lcdInput.remove(12);

  if (_lcdInput.length() < 12)
  {
    for (byte i = _lcdInput.length() + 1; i <= 12; i++)
    {
      _lcdInput.concat(' ');
    }
  }

  byte _start = 0x80;

  PORTB &= ~(1 << PB0);
  SPI.transfer(0x80);
  PORTB |= (1 << PB0);

  for (byte i = 0; i < _lcdInput.length(); i++)
  {
    PORTL |= (1 << PL0); // highlighted here for the error
    PORTB &= ~(1 << PB0);
    SPI.transfer(_lcdInput.charAt(i));
    PORTB |= (1 << PB0);
    delayMicroseconds(42);
    PORTL &= ~(1 << PL0);
  }
}

exit status 1
‘PORTL’ was not declared in this scope

An Uno only has Ports A, B, C, D, so PORTL command cannot work.

These are set up by SPI.begin() so you don't need to declare them:
//PORTB
#define DATAOUT 11 // MOSI
#define SPICLOCK 13 //sck
#define ssPin 10 // PB0

And MISO on 12.
Well, ssPin you need, as that is called out to turn slave select on & off.

Is SS here
digitalWrite(SS, LOW); // enable Slave Select

the same as ssPin?
I'm surprised that isn't showing up with an Undefined Variable kind of error.

Thank you Crossroads

I’ve adjusted the code based on your feedback for the declare info and have moved PORTL to PORTD using pins 0 & 1 as they are PD0 and PD1. This is based on 48 & 49 on the Mega being PL0 & PL1

Unfortunately I’m assuming this isn’t correct as the screen isn’t working. Is this because the pins are also RX and TX?

#include <SPI.h>
#include <Time.h>
#include <Wire.h>
#include "RTClib.h"

RTC_DS3231 rtc;


////PORTD
#define mode0 1
#define mode1 0


// User configurable

String Oil = "TESTING";

void setup() {

  // Control pins for the display SPI
  pinMode(mode0, OUTPUT);
  pinMode(mode1, OUTPUT);



  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE3)); //I couldn't see a defined max speed in the NJU6623 datasheet, 10Meg is quite an increase on the original 9600, so need to monitor incase this is not stable.
 // Serial.begin (115200, SERIAL_8N1);
  //Serial2.begin (4800, SERIAL_8E1);
  Serial.begin(9600);
    delay(300);
}

void loop()
{
//// Send text to screen
populateFixedLcdDisplay(Oil);
Serial.println(Oil);
 delay(600);
}

  // LCD Display
void populateFixedLcdDisplay(String _lcdInput)
{
  _lcdInput.remove(12);

  if (_lcdInput.length() < 12)
  {
    for (byte i = _lcdInput.length() + 1; i <= 12; i++)
    {
      _lcdInput.concat(' ');
    }
  }

  byte _start = 0x80;

  PORTB &= ~(1 << PB0);
  SPI.transfer(0x80);
  PORTB |= (1 << PB0);

  for (byte i = 0; i < _lcdInput.length(); i++)
  {
    PORTD |= (1 << PD0);
    PORTB &= ~(1 << PB0);
    SPI.transfer(_lcdInput.charAt(i));
    PORTB |= (1 << PB0);
    delayMicroseconds(42);
    PORTD &= ~(1 << PD0);
  }
}

for the “padding” issue, I’ve added comments:

void populateFixedLcdDisplay(String _lcdInput)
{
  _lcdInput.remove(12);           // truncates the String _lcdInput to a maximum 12 characters
  if (_lcdInput.length() < 12)     
  {
    // if _lcdInput is less 12 characters, pad it out to 12 characters with blanks
    for (byte i = _lcdInput.length() + 1; i <= 12; i++)
    {
      _lcdInput.concat(' ');
    }
  }

  // more code 
}

So, for example:

"hello" would give "hello       "
"arduino_rules_here"  would give  "arduino_rule"

You are using 2 different methods of sending SPI commands.

You are using this in the loop() and this is the “simple” library function.

   digitalWrite(SS, LOW); // enable Slave Select
   // send test string
   for (const char * p = "Hello, World\r" ; c = *p; p++)
   {
      SPI.transfer (c);
      Serial.print(c);
   }
   digitalWrite(SS, HIGH); // disable Slave Select

All this more in populateFixedLcdDisplay().

byte _start = 0x80;

  PORTB &= ~(1 << PB0);
  SPI.transfer(0x80);
  PORTB |= (1 << PB0);

  for (byte i = 0; i < _lcdInput.length(); i++)
  {
    PORTL |= (1 << PL0); // highlighted here for the error
    PORTB &= ~(1 << PB0);
    SPI.transfer(_lcdInput.charAt(i));
    PORTB |= (1 << PB0);
    delayMicroseconds(42);
    PORTL &= ~(1 << PL0);
  }

These are equivalent except that the second version appears to be addressing 2 slaves (with different SS pins). Why not just use the method in the loop().

This: PORTL |= (1 << PL0) ; // highlighted here for the error

is simply setting the SS (slave select pin) for the slave device. You appear to be using pin 10 for this purpose. So you could change it to

PORTB |= (1 << PB2) ; // pin 10 (PB2) on uno.

Can you supply a diagram of how you have wired all this up.

@vj4

Other post/duplicate DELETED
Please do NOT cross post / duplicate as it wastes peoples time and efforts to have more than one post for a single topic.

Continued cross posting could result in a time out from the forum.

Could you take a few moments to Learn How To Use The Forum.
It will help you get the best out of the forum in the future.
Other general help and troubleshooting advice can be found here.

6v6gt:
for the “padding” issue, I’ve added comments:

void populateFixedLcdDisplay(String _lcdInput)

{
  _lcdInput.remove(12);          // truncates the String _lcdInput to a maximum 12 characters
  if (_lcdInput.length() < 12)   
  {
    // if _lcdInput is less 12 characters, pad it out to 12 characters with blanks
    for (byte i = _lcdInput.length() + 1; i <= 12; i++)
    {
      _lcdInput.concat(’ ');
    }
  }

// more code
}





So, for example: 



“hello” would give "hello      "
“arduino_rules_here”  would give  “arduino_rule”





You are using 2 different methods of sending SPI commands.


You are using this in the loop() and this is the "simple" library function.



digitalWrite(SS, LOW); // enable Slave Select
  // send test string
  for (const char * p = “Hello, World\r” ; c = *p; p++)
  {
      SPI.transfer (c);
      Serial.print(c);
  }
  digitalWrite(SS, HIGH); // disable Slave Select





All this more in populateFixedLcdDisplay(). 



byte _start = 0x80;

PORTB &= ~(1 << PB0);
  SPI.transfer(0x80);
  PORTB |= (1 << PB0);

for (byte i = 0; i < _lcdInput.length(); i++)
  {
    PORTL |= (1 << PL0); // highlighted here for the error
    PORTB &= ~(1 << PB0);
    SPI.transfer(_lcdInput.charAt(i));
    PORTB |= (1 << PB0);
    delayMicroseconds(42);
    PORTL &= ~(1 << PL0);
  }




These are equivalent except that the second version appears to be addressing 2 slaves (with different SS pins). Why not just use the method in the loop().


This: PORTL |= (1 << PL0) ; // highlighted here for the error 

is simply setting the SS (slave select pin) for the slave device. You appear to be using pin 10 for this purpose. So you could change it to 

PORTB |= (1 << PB2) ; // pin 10 (PB2) on uno.

Can you supply a diagram of how you have wired all this up.

Thanks 6v6gt,

The screen requires 2 additional pins for the data transfer RS (register selection) and AC (CGRAM selection. These are the PORTL pins 49 & 48 on the mega. I’ve moved these to 9 & 8 on the uno and amended the code to the below.

#include <SPI.h>
#include <Time.h>
#include <Wire.h>
#include "RTClib.h"

RTC_DS3231 rtc;


////PORTB
#define mode0 9
#define mode1 8


// User configurable

String Oil = "Thanks 6v6gt";

void setup() {
// start clock
 rtc.begin();

  // Control pins for the display SPI
  pinMode(mode0, OUTPUT);
  pinMode(mode1, OUTPUT);


  SPI.begin();
  SPI.beginTransaction(SPISettings(9600, MSBFIRST, SPI_MODE3)); //I couldn't see a defined max speed in the NJU6623 datasheet, 10Meg is quite an increase on the original 9600, so need to monitor incase this is not stable.
 // Serial.begin (115200, SERIAL_8N1);
  //Serial2.begin (4800, SERIAL_8E1);
  Serial.begin(9600);
    delay(300);
}

void loop()
{

populateFixedLcdDisplay(Oil);
delay(500);

}
 


  // LCD Display
void populateFixedLcdDisplay(String _lcdInput)
{
  _lcdInput.remove(12);

  if (_lcdInput.length() < 12)
  {
    for (byte i = _lcdInput.length() + 1; i <= 12; i++)
    {
      _lcdInput.concat(' ');
    }
  }

  byte _start = 0x80;

  PORTB &= ~(1 << PB2);
  SPI.transfer(0x80);
  PORTB |= (1 << PB2);

  for (byte i = 0; i < _lcdInput.length(); i++)
  {
    PORTB |= (1 << PB1);
    PORTB &= ~(1 << PB2);
    SPI.transfer(_lcdInput.charAt(i));
    PORTB |= (1 << PB2);
    delayMicroseconds(42);
    PORTB &= ~(1 << PB1);
  }
}

This is now displaying the message Thanks 6v6gt on the screen using an UNO. There’s still something not quite right as a bunch of the other parts of the LCD flashing. I’ll have to figure this out later. I think I can now start using the Uno as a test master for the Mega.

I dont have a wiring diagram as such just a table of colours to pins with a name for the mega (attached).

The difference to the Uno IS below

AC = 8

RS = 9

SPI MOSI = 11

SPI CLK = 13

SPI SS = 10

Now I need to investigate if i can read only the specific address’s 0x80 and let everything else pass through without having to read it all and then send it all out agian.

Mega.jpg