Getting Serial, LCD and SoftwareSerial to work together

I use an Arduino Uno R2 with a Flytron GPS and a Sparkfun Nokia LCD with the TinyGPS library (version 12) and ColorLCDShield library.
The GPS is attached to pins 2 and 3 with SoftwareSerial. The LCD shield is attached as such: Vbat to 5v of the Arduino, 3.3V to the 3.3V pin on the Arduino, GND to gnd obviously. Then reset to pin 8, DIO to pin 11, SCK to pin 13 and CS to pin 9.
I also use an external 12V power supply because I noticed the GPS wouldn't get a fix when I just powered everything of a USB port.

The idea is to have GPS data show on the lcd, but for now I just output it to Serial.
I can get the GPS to work, or the LCD shield, but not both together.

This is the code, it's a combination of sample code found for both the gps and lcd shield:

#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <ColorLCDShield.h>


TinyGPS gps;
LCDShield lcd;

#define RXPIN 2
#define TXPIN 3 
SoftwareSerial nss(RXPIN, TXPIN);

long GPS_lat, GPS_lon = 0;
unsigned long GPS_speed, GPS_course, GPS_number_of_satellites = 0;
unsigned long GPS_chars;
unsigned short GPS_sentences, GPS_failed_checksum;
unsigned long previousTime, currentTime;
int GPS_year = 0 ;
byte GPS_month, GPS_day, GPS_hour, GPS_minute, GPS_second, GPS_hundredths = 0 ;
unsigned long GPS_fix_age = 0;

byte LCD_cont = 50;  // Good center value for contrast

void setup()
{
  previousTime = currentTime = 0;

  nss.begin(9600);
  Serial.begin(9600);

  lcd.init(EPSON);  // Initialize the LCD, try using PHILLIPS if it's not working
  lcd.contrast(LCD_cont);  // Initialize contrast
  lcd.clear(WHITE);  // Set background to white
  lcd.setPixel(GREEN, 66, 66);
  lcd.setCircle(66, 66, 30, GREEN);
  lcd.setCircle(66, 66, 60, GREEN);
  lcd.setCircle(66, 66, 1, GREEN);
  lcd.setStr("test", 20, 20, WHITE, BLACK);
}


void loop()
{
  currentTime = millis();
  while (nss.available())
  {
    //Serial.println("ding dong");
    int c = nss.read();
    if (gps.encode(c))
    {
      Serial.println("GPS data ontvangen!");
      // retrieves +/- lat/long in 100.000ths of a degree
      gps.get_position(&GPS_lat, &GPS_lon, &GPS_fix_age);

      gps.crack_datetime(&GPS_year, &GPS_month, &GPS_day, &GPS_hour, &GPS_minute, &GPS_second, &GPS_hundredths, &GPS_fix_age);
      GPS_hour = GPS_hour+2;
      if (GPS_hour >= 24) GPS_hour = GPS_hour -24;
      // returns speed in 100ths of a knot
      GPS_speed = gps.speed();

      // course in 100ths of a degree
      GPS_course = gps.course();

      GPS_number_of_satellites = gps.satellites();

    }
  }
  if (currentTime - previousTime >= 1000) {
    if (GPS_number_of_satellites > 0) {
      Serial.print (GPS_hour);
      Serial.print (":");
      Serial.print (GPS_minute);
      Serial.print (":");
      Serial.print (GPS_second);
      Serial.print (" - ");
      Serial.print ("Latitude: ");
      Serial.print (GPS_lat);
      Serial.print (" Longitude: ");
      Serial.print (GPS_lon);
      Serial.print (" Fix age: ");
      Serial.print (GPS_fix_age);
      Serial.print (" Satellieten: ");
      Serial.println (GPS_number_of_satellites);
    } 
    else {
      Serial.println ("No GPS fix");
    }
      previousTime = currentTime;
  }
}

Symptom: the LCD keeps flashing on and off, drawing only a small part of the circle.

So I started commenting parts. If I comment the lcd.SetStr:

#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <ColorLCDShield.h>


TinyGPS gps;
LCDShield lcd;

#define RXPIN 2
#define TXPIN 3 
SoftwareSerial nss(RXPIN, TXPIN);

long GPS_lat, GPS_lon = 0;
unsigned long GPS_speed, GPS_course, GPS_number_of_satellites = 0;
unsigned long GPS_chars;
unsigned short GPS_sentences, GPS_failed_checksum;
unsigned long previousTime, currentTime;
int GPS_year = 0 ;
byte GPS_month, GPS_day, GPS_hour, GPS_minute, GPS_second, GPS_hundredths = 0 ;
unsigned long GPS_fix_age = 0;

byte LCD_cont = 50;  // Good center value for contrast

void setup()
{
  previousTime = currentTime = 0;

  nss.begin(9600);
  Serial.begin(9600);

  lcd.init(EPSON);  // Initialize the LCD, try using PHILLIPS if it's not working
  lcd.contrast(LCD_cont);  // Initialize contrast
  lcd.clear(WHITE);  // Set background to white
  lcd.setPixel(GREEN, 66, 66);
  lcd.setCircle(66, 66, 30, GREEN);
  lcd.setCircle(66, 66, 60, GREEN);
  lcd.setCircle(66, 66, 1, GREEN);
//  lcd.setStr("test", 20, 20, WHITE, BLACK); <<<<-------------------------------------------------
}


void loop()
{
  currentTime = millis();
  while (nss.available())
  {
    //Serial.println("ding dong");
    int c = nss.read();
    if (gps.encode(c))
    {
      Serial.println("GPS data ontvangen!");
      // retrieves +/- lat/long in 100.000ths of a degree
      gps.get_position(&GPS_lat, &GPS_lon, &GPS_fix_age);

      gps.crack_datetime(&GPS_year, &GPS_month, &GPS_day, &GPS_hour, &GPS_minute, &GPS_second, &GPS_hundredths, &GPS_fix_age);
      GPS_hour = GPS_hour+2;
      if (GPS_hour >= 24) GPS_hour = GPS_hour -24;
      // returns speed in 100ths of a knot
      GPS_speed = gps.speed();

      // course in 100ths of a degree
      GPS_course = gps.course();

      GPS_number_of_satellites = gps.satellites();

    }
  }
  if (currentTime - previousTime >= 1000) {
    if (GPS_number_of_satellites > 0) {
      Serial.print (GPS_hour);
      Serial.print (":");
      Serial.print (GPS_minute);
      Serial.print (":");
      Serial.print (GPS_second);
      Serial.print (" - ");
      Serial.print ("Latitude: ");
      Serial.print (GPS_lat);
      Serial.print (" Longitude: ");
      Serial.print (GPS_lon);
      Serial.print (" Fix age: ");
      Serial.print (GPS_fix_age);
      Serial.print (" Satellieten: ");
      Serial.println (GPS_number_of_satellites);
    } 
    else {
      Serial.println ("No GPS fix");
    }
    previousTime = currentTime;
  }
}

Symptom: Everything works, with GPS info over Serial, but there's obviously no text on the lcd.

With only the gps code commented:

#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <ColorLCDShield.h>


TinyGPS gps;
LCDShield lcd;

#define RXPIN 2
#define TXPIN 3 
SoftwareSerial nss(RXPIN, TXPIN);

long GPS_lat, GPS_lon = 0;
unsigned long GPS_speed, GPS_course, GPS_number_of_satellites = 0;
unsigned long GPS_chars;
unsigned short GPS_sentences, GPS_failed_checksum;
unsigned long previousTime, currentTime;
int GPS_year = 0 ;
byte GPS_month, GPS_day, GPS_hour, GPS_minute, GPS_second, GPS_hundredths = 0 ;
unsigned long GPS_fix_age = 0;

byte LCD_cont = 50;  // Good center value for contrast

void setup()
{
  previousTime = currentTime = 0;

  nss.begin(9600);
  Serial.begin(9600);

  lcd.init(EPSON);  // Initialize the LCD, try using PHILLIPS if it's not working
  lcd.contrast(LCD_cont);  // Initialize contrast
  lcd.clear(WHITE);  // Set background to white
  lcd.setPixel(GREEN, 66, 66);
  lcd.setCircle(66, 66, 30, GREEN);
  lcd.setCircle(66, 66, 60, GREEN);
  lcd.setCircle(66, 66, 1, GREEN);
  lcd.setStr("test", 20, 20, WHITE, BLACK);
}


void loop()
{
  currentTime = millis();
//  while (nss.available())
//  {
//    //Serial.println("ding dong");
//    int c = nss.read();
//    if (gps.encode(c))
//    {
//      Serial.println("GPS data ontvangen!");
//      // retrieves +/- lat/long in 100.000ths of a degree
//      gps.get_position(&GPS_lat, &GPS_lon, &GPS_fix_age);
//
//      gps.crack_datetime(&GPS_year, &GPS_month, &GPS_day, &GPS_hour, &GPS_minute, &GPS_second, &GPS_hundredths, &GPS_fix_age);
//      GPS_hour = GPS_hour+2;
//      if (GPS_hour >= 24) GPS_hour = GPS_hour -24;
//      // returns speed in 100ths of a knot
//      GPS_speed = gps.speed();
//
//      // course in 100ths of a degree
//      GPS_course = gps.course();
//
//      GPS_number_of_satellites = gps.satellites();
//
//    }
//  }
  if (currentTime - previousTime >= 1000) {
    if (GPS_number_of_satellites > 0) {
      Serial.print (GPS_hour);
      Serial.print (":");
      Serial.print (GPS_minute);
      Serial.print (":");
      Serial.print (GPS_second);
      Serial.print (" - ");
      Serial.print ("Latitude: ");
      Serial.print (GPS_lat);
      Serial.print (" Longitude: ");
      Serial.print (GPS_lon);
      Serial.print (" Fix age: ");
      Serial.print (GPS_fix_age);
      Serial.print (" Satellieten: ");
      Serial.println (GPS_number_of_satellites);
    } 
    else {
      Serial.println ("No GPS fix");
    }
    previousTime = currentTime;
  }
}

Symptom: The screen just hangs, blue color, no serial info is sent (Tx on the Arduino doesn't blink)

It seems that the lcd.SetStr and setChar functions are the key, as everything starts working as soon as I comment the lcd.setStr line.
If I change lcd.setStr("test", 20, 20, WHITE, BLACK); to lcd.setChar('t', 20, 20, WHITE, BLACK); the circles draw partially, the 't' appears on the screen, but Serial is outputting crap.

gps_locator.ino (2.41 KB)

Any reason for not using hardware serial? (Pins 0 and 1)

FYI, these are the setStr and setChar functions from the ColorLCDShield library:

void LCDShield::setChar(char c, int x, int y, int fColor, int bColor)
{
	y	=	(COL_HEIGHT - 1) - y; // make display "right" side up
	x	=	(ROW_LENGTH - 2) - x;

	int             i,j;
	unsigned int    nCols;
	unsigned int    nRows;
	unsigned int    nBytes;
	unsigned char   PixelRow;
	unsigned char   Mask;
	unsigned int    Word0;
	unsigned int    Word1;
	unsigned char   *pFont;
	unsigned char   *pChar;

	// get pointer to the beginning of the selected font table
	pFont = (unsigned char *)FONT8x16;
	// get the nColumns, nRows and nBytes
	nCols = *pFont;
	nRows = *(pFont + 1);
	nBytes = *(pFont + 2);
	// get pointer to the last byte of the desired character
	pChar = pFont + (nBytes * (c - 0x1F)) + nBytes - 1;

	if (driver)	// if it's an epson
	{
		// Row address set (command 0x2B)
		LCDCommand(PASET);
		LCDData(x);
		LCDData(x + nRows - 1);
		// Column address set (command 0x2A)
		LCDCommand(CASET);
		LCDData(y);
		LCDData(y + nCols - 1);
	
		// WRITE MEMORY
		LCDCommand(RAMWR);
		// loop on each row, working backwards from the bottom to the top
		for (i = nRows - 1; i >= 0; i--) {
			// copy pixel row from font table and then decrement row
			PixelRow = *pChar++;
			// loop on each pixel in the row (left to right)
			// Note: we do two pixels each loop
			Mask = 0x80;
			for (j = 0; j < nCols; j += 2) 
			{
				// if pixel bit set, use foreground color; else use the background color
				// now get the pixel color for two successive pixels
				if ((PixelRow & Mask) == 0)
					Word0 = bColor;
				else
					Word0 = fColor;
				Mask = Mask >> 1;
				if ((PixelRow & Mask) == 0)
					Word1 = bColor;
				else
					Word1 = fColor;
				Mask = Mask >> 1;
				// use this information to output three data bytes
				LCDData((Word0 >> 4) & 0xFF);
				LCDData(((Word0 & 0xF) << 4) | ((Word1 >> 8) & 0xF));
				LCDData(Word1 & 0xFF);
			}
		}
	}
	else
	{
		// Row address set (command 0x2B)
		LCDCommand(PASETP);
		LCDData(x);
		LCDData(x + nRows - 1);
		// Column address set (command 0x2A)
		LCDCommand(CASETP);
		LCDData(y);
		LCDData(y + nCols - 1);
	
		// WRITE MEMORY
		LCDCommand(RAMWRP);
		// loop on each row, working backwards from the bottom to the top
		pChar+=nBytes-1;  // stick pChar at the end of the row - gonna reverse print on phillips
		for (i = nRows - 1; i >= 0; i--) {
			// copy pixel row from font table and then decrement row
			PixelRow = *pChar--;
			// loop on each pixel in the row (left to right)
			// Note: we do two pixels each loop
			Mask = 0x01;  // <- opposite of epson
			for (j = 0; j < nCols; j += 2) 
			{
				// if pixel bit set, use foreground color; else use the background color
				// now get the pixel color for two successive pixels
				if ((PixelRow & Mask) == 0)
					Word0 = bColor;
				else
					Word0 = fColor;
				Mask = Mask << 1; // <- opposite of epson
				if ((PixelRow & Mask) == 0)
					Word1 = bColor;
				else
					Word1 = fColor;
				Mask = Mask << 1; // <- opposite of epson
				// use this information to output three data bytes
				LCDData((Word0 >> 4) & 0xFF);
				LCDData(((Word0 & 0xF) << 4) | ((Word1 >> 8) & 0xF));
				LCDData(Word1 & 0xFF);
			}
		}
	}
}

void LCDShield::setStr(char *pString, int x, int y, int fColor, int bColor)
{
	x = x + 16;
	y = y + 8;
	// loop until null-terminator is seen
	while (*pString != 0x00) {
		// draw the character
		setChar(*pString++, x, y, fColor, bColor);
		// advance the y position
		y = y + 8;
		// bail out if y exceeds 131
		if (y > 131) break;
	}
}

I've only got one hardware serial because it's a Uno, and I want to use it to communicate with my pc for debugging.

Do you have a second Uno (or similar)?

You can debug via I2C or SPI if you do.

SoftwareSerial is not going to be as reliable as hardware serial.

Yeah, I have another Uno.
I don't think I can use SPI as it's already used by the lcd shield. I could try I2C... but I feel I'm circumventing the problem then :slight_smile:
Is there something I could change to the lcd.setChar code to get it working together nicely?

Circumventing the problem that software-based serial is less reliable than hardware-based serial? That's a problem I would want to circumvent, personally.

Alright, thanks for the advice. I will try using the hardware serial only and if that works, use the lcd for debugging.

Do you have any background on why SoftwareSerial is unreliable?

Well ... You are tying up the processor in a tight loop waiting for bits to arrive at precise intervals. Things can go wrong with that. And you have dedicated hardware designed to do exactly that. By experts.

Let me ask you this: "why buy a dog, and bark yourself?"

And here I was thinking SoftwareSerial used interrupts... I've still a lot to learn.

And here I was thinking SoftwareSerial used interrupts... I've still a lot to learn.

It does - to know when to start listening (in the tight loop) for the bits that make up the byte.

It does use interrupts. I never said it didn't. But once the initial interrupt arrives it sits in a loop. The interrupt tells it the first bit arrives, then this:

    // Read each of the 8 bits
    for (uint8_t i=0x1; i; i <<= 1)
    {
      tunedDelay(_rx_delay_intrabit);
      DebugPulse(_DEBUG_PIN2, 1);
      uint8_t noti = ~i;
      if (rx_pin_read())
        d |= i;
      else // else clause added to ensure function timing is ~balanced
        d &= noti;
    }

No interrupts there, a timed loop.

Ninja'd by PaulS.

I should also point out that whilst interrupts are great in their own way, they can have an initial latency which depends on whether another interrupt (eg. a timer) is in progress, and other things. The hardware doesn't suffer from that.

The hardware doesn't suffer from that.

OK. I'll bite. Since HardwareSerial uses interrupts to send and receive data, why is it not impacted by other interrupts happening?

why is it not impacted by other interrupts happening

Because the USART is implemented in hardware as its own state machine.
"Wait for edge of start bit"
"count off half a bit period and sample again, if still start bit, start counting whole bit periods"

Then, will this advantage be gone with the Leonardo?

Ok, there's definitely something weird going on.
I have the Flytron GPS attached to pins 0 and 1 and have disabled SoftwareSerial.
For debugging I have attached a led to pin 8 which is HIGH standard, and goes LOW when serial data is received from the gps.

What happens is that my lcd powers on perfectly, showing the circles and "No gps fix".
But as soon as the gps finds a fix, the led on pin 8 goes off (meaning it's receiving serial data from the gps), but the lcd starts flashing, eventually going completely off.
I noticed that the led on the Arduino board (attached to pin 13, where also the SCK pin of the lcd is attached) is flashing erratically.

This is my code:

//#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <ColorLCDShield.h>


TinyGPS gps;
LCDShield lcd;

//#define RXPIN 2
//#define TXPIN 3 
//SoftwareSerial nss(RXPIN, TXPIN);

long GPS_lat, GPS_lon = 0;
unsigned long GPS_speed, GPS_course, GPS_number_of_satellites = 0;
unsigned long GPS_chars;
unsigned short GPS_sentences, GPS_failed_checksum;
unsigned long previousTime, currentTime;
int GPS_year = 0 ;
byte GPS_month, GPS_day, GPS_hour, GPS_minute, GPS_second, GPS_hundredths = 0 ;
unsigned long GPS_fix_age = 0;

byte LCD_cont = 50;  // Good center value for contrast

char buffer[12];

int ledPin = 4;

void setup()
{
  previousTime = currentTime = 0;

//  nss.begin(9600);
  Serial.begin(9600);

  lcd.init(EPSON);  // Initialize the LCD, try using PHILLIPS if it's not working
  lcd.contrast(LCD_cont);  // Initialize contrast
  refresh_screen();
  
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);
}


void loop()
{
  currentTime = millis();
  while (Serial.available())
  {
    digitalWrite(ledPin, LOW);
    int c = Serial.read();
    if (gps.encode(c))
    {
//      Serial.println("GPS data ontvangen!");
//      lcd.setStr("GPS data received!", 0, 0, WHITE, BLACK);
      // retrieves +/- lat/long in 100.000ths of a degree
      gps.get_position(&GPS_lat, &GPS_lon, &GPS_fix_age);

      gps.crack_datetime(&GPS_year, &GPS_month, &GPS_day, &GPS_hour, &GPS_minute, &GPS_second, &GPS_hundredths, &GPS_fix_age);
      GPS_hour = GPS_hour+2;
      if (GPS_hour >= 24) GPS_hour = GPS_hour -24;
      // returns speed in 100ths of a knot
      GPS_speed = gps.speed();

      // course in 100ths of a degree
      GPS_course = gps.course();

      GPS_number_of_satellites = gps.satellites();

    }
  }
  if (currentTime - previousTime >= 1000) {
    if (GPS_number_of_satellites > 0) {
//    Serial.print (GPS_hour);
//    Serial.print (":");
//    Serial.print (GPS_minute);
//    Serial.print (":");
//    Serial.print (GPS_second);
//    Serial.print (" - ");
//    Serial.print ("Latitude: ");
//    Serial.print (GPS_lat);
//    Serial.print (" Longitude: ");
//    Serial.print (GPS_lon);
//    Serial.print (" Fix age: ");
//    Serial.print (GPS_fix_age);
//    Serial.print (" Satellieten: ");
//    Serial.println (GPS_number_of_satellites);
//    ltoa(GPS_number_of_satellites, buffer, 10);      
//    lcd.setStr(buffer, 40, 20, WHITE, BLACK);
      lcd.setStr("Satellites found!", 40, 20, WHITE, BLACK);
    } 
    else {
//      Serial.println ("No GPS fix");
      lcd.setStr("No GPS fix", 40, 20, WHITE, BLACK);
    }
    previousTime = currentTime;
  }
  digitalWrite(ledPin, HIGH);
}

void refresh_screen() {
  lcd.clear(WHITE);  // Set background to white
  lcd.setPixel(GREEN, 66, 66);
  lcd.setCircle(66, 66, 30, GREEN);
  lcd.setCircle(66, 66, 60, GREEN);
  lcd.setCircle(66, 66, 1, GREEN);
}

I'm pulling my hair out from frustration!

I'm glad you asked that Paul. :slight_smile:

First, SoftwareSerial needs an interrupt to fire, and be serviced promptly after the start bit. This is to set in train the reception of the other bits. The hardware can happily process all bits (start/data/stop) without raising a single interrupt or needing one to be raised. Also the hardware is double-buffered in the receive direction so it could actually be receiving a second byte before the program needs to do a single thing.

The hardware accepts (processes) all data bits, and the stop bit, before raising an interrupt (data available) for which the processor has an entire extra byte's time (because of the double buffering) to service it.

So any delay (and there is always some delay) in processing an interrupt is going to throw out SoftwareSerial's chances of being correctly positioned in the middle of the bit stream for the incoming byte. You can compensate to an extent, but unless the processor is asleep there is going to be an un-knowable amount of jitter in the interrupt.

AWOL:
Because the USART is implemented in hardware as its own state machine.

Oops, didn't notice AWOL's reply (new forum page) before I started typing.

EvilPharmacist:
Then, will this advantage be gone with the Leonardo?

You have two sorts of serial on the Leonardo. Serial is via the USB, Serial1 is the normal hardware serial on pins 0 and 1. So I don't see why the advantage would be gone.