LCD 'word-wrap' rather than 'character-wrap'

Hello,

First, please note that I have Googled this, and it is through Google and reading others' posts on the matter that i have managed to get this far as I am very new to programming & Arduino.

I am trying to write a small program that receives an RSS and displays it on a 16x2 screen using Arduino Uno. I have successfully achieved this step. This result split a word over 2 both lines if needed.

I then want it to print each 'word' rather than each character so that if there were not enough characters left on the line to display the word in full, then it would print the word on the next line, (0,1)

I have 90% succeeded in this but it is just not quite right -words still seems to run off a little. If the cursor is set to line 2 and there is run off then i would like a small pause, for the screen to clear and then print on a fresh screen at position (0,0)

Also, after each feed I want the lcd to clear and reset to (0,0) but this is proving problematic too (so I have removed that all together from the code... one problem at a time!!!)

Could someone please take a look at the code below and tell me where I have gone wrong.

Thank you

C++ Code:

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const byte numChars = 32;
char receivedChars[numChars]; // as array to store the recevied characters

boolean newData = false;
const int initialMarker = 1;
boolean line1 = true;
boolean line2 = false;
int charRemaining = 16;

void setup(){
  delay (2000);
  Serial.begin(9600);
  lcd.begin(16,2);
  lcd.setCursor(0,0);
  pinMode(13, OUTPUT); 
  pinMode(10, OUTPUT); 
  Serial.println(initialMarker); // print startMarker once
}

void loop(){
    recvWithEndMarker(); 
}

void newFeedCheck(char incomingByte){
   if (incomingByte == '>') {
       lcd.clear();
       lcd.setCursor(0,0); 
       charRemaining = 16;
   }  
}
   
void recvWithEndMarker(){  
  static byte ndx = 0;
  char endMarker = '~';
  char rc;
  
  while (Serial.available() > 0 && newData == false){
    rc = Serial.read();
    if (rc != endMarker)  {
      if ((rc != '<') && (rc != '>')) {
        receivedChars[ndx] = rc;
        ndx++;
      }
        
      if (ndx >= numChars){
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminates the string
      ndx = 0;
      newData = true;
    }
    }
    workOutLines();
  }

int strLength(char*buffer){
  int i = 0, length = 0;
  while (buffer != '\0'){
    if (buffer[i+1] == '\0'){
      length = i+1;
      return length;
    }
    i++;
  }
}

  
void workOutLines(){
  int wordSize;
  if (newData == true){
    wordSize = strLength(receivedChars);
    newData = false;
    showData(wordSize); 
  }
}

void showData(int receivedWordSize){
    
  if (charRemaining < 0 ) {
     if (line1 == true  && line2 == false) {
        line1 = false;
        line2 = true;
        charRemaining = 16;
        lcd.setCursor(0,1);
        lcd.print (receivedChars);
        lcd.print (" ");
        charRemaining = charRemaining-receivedWordSize;
        charRemaining --;
     }
     else if (line1 == false && line2 == true) {
        line1 = true;
        line2 = false;
        charRemaining = 16;       
        lcd.clear ();
        lcd.setCursor(0,0);
        lcd.print (receivedChars);
        lcd.print (" ");
        charRemaining = charRemaining-receivedWordSize;
        charRemaining --;
      }
     }
     else{
       lcd.print (receivedChars);
       lcd.print (" ");
       charRemaining = charRemaining-receivedWordSize;
       charRemaining --;
     }    
}

Python RSS Script

#
# from http://www.pythonforbeginners.com/feedparser/using-feedparser-in-python
# installed feedparser with `pip`, after installing pip with apt-get
#
import serial
import feedparser
import time
ser = serial.Serial("\\.\COM3", 9600)


i = 1
y = 0
time.sleep(3)

while i == 1:
  y = int(ser.read())
  
  while y == 1:
        
    sportFeed = feedparser.parse('http://feeds.bbci.co.uk/sport/0/football/rss.xml')

    posts = []

    for z in range(0,len(sportFeed)):
      
      topic = sportFeed['entries'][z].title.encode('utf-8')
      newTopic = topic.replace(" ","~")
      print  newTopic
      ser.write (str("<"))
      ser.write (str(newTopic))
      ser.write (str('~>'))
      time.sleep(6)

    time.sleep(30)

I just googled Arduino LCD word wrap and got over 15,000 hits. Clearly, this has been done before and you'd probably find something that would help there.

Yes, it is having read through those posts which has enabled me to get this far. I am new to programming and Arduino. Though am I now unable to find a post that directly relates to my problem using the method I have used.

It's almost impossible to find something exactly what you are looking for.

It's not that difficult to do really. But it will change slightly depending on the way you want to go.

If you are not using a LCD either 16x2 / 20x4 or similar, then you will need to calculate the number of characters that will fit on a line.

Next you need to get the words, this can be done a few ways, the most common way uses strtok().

Once you have a word(s) you need to know how long it is. You get that from strlen( word )

Now that you can get the length of the word, you need to do some math before you show the word. If the word length fits within the lines character limit and does not go past that limit, show the word and add a blank space. If the word to be added does go past the limit then put it on a new line.

Thanks for replying Hazards Mind - that's exactly what I have done. Apart from i didnt use strtok(). Its 95% there, though sometime it over-runs.

I have a Characters Remaining variable set to 16 (as per line), and then 2x bools for Line 1 and Line 2. The code checks the size of the word against how many Characters are remaining on that line, if there is not enough it checks to see whether the current line is line 1 or 2 and then sets the cursor....

...but it's just still not quite right....

radowns82:
...but it's just still not quite right....

How about something like this:

#define MAX_MESSAGE_SIZE 128
#define DISPLAY_WIDTH 16

bool gotPacket = false;
char message[MAX_MESSAGE_SIZE] = "";
byte index = 0;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  if(Serial.available())
  {
    getPacket();
  }
  if(gotPacket)
  {
    //Serial.println(message);
    printInSegments(message);
    strcpy(message, "");
    gotPacket = false;
  }
}

void getPacket()
{
  if(Serial.peek() == ';')
  {
    Serial.read();
    gotPacket = true;
    index = 0;
    return;
  }
  message[index++] = Serial.read();
  message[index] = NULL;
  if(index >= MAX_MESSAGE_SIZE - 1)
  {
    gotPacket = true;
    index = 0;
  }  
}

void printInSegments(char* string)
{
  int lineLength = 0;
  char* ptr = strtok(string, " ");
  lineLength += (strlen(ptr));
  while(ptr != NULL)
  {
    Serial.print(ptr);
    Serial.print(" ");
    ptr = strtok(NULL, " ");
    lineLength += ((strlen(ptr) + 1));
    if(lineLength > DISPLAY_WIDTH)
    {
      Serial.println();
      lineLength = (strlen(ptr) + 1);
    }
  }
  Serial.println();
}

You just need to modify for LCD display

So I finally figured this out. For anyone else looking to do something similar, here is my code.

Thanks for pointing out for me to use strtok and *string.*length

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const byte numChars = 32;
char receivedChars[numChars]; // as array to store the recevied characters

boolean newData = false;
const int initialMarker = 1;
boolean lineOne = true;
boolean lineTwo = false;
int charRemaining = 16;


void setup(){
  delay (1000);
  Serial.begin(9600);
  lcd.begin(16,2);
  lcd.setCursor(0,0);
  pinMode(13, OUTPUT); 
  pinMode(10, OUTPUT); 
  Serial.println(initialMarker); // print startMarker once
}

void loop(){
    recvWithEndMarker(); 
}

   
void recvWithEndMarker(){  
  static byte ndx = 0;
  char endMarker = '~';
  char rc;
  
  while (Serial.available() > 0 && newData == false){
    rc = Serial.read();
    if (rc != endMarker)  {
      if (rc == '<'){
        delay(1000);
        lcd.setCursor(0,0);
        lcd.clear();
      }else if (rc != '<') {
        receivedChars[ndx] = rc;
        ndx++;
      }
            
      if (ndx >= numChars){
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminates the string
      ndx = 0;
      newData = true;
    }
    }
    workOutLines();
  }

void workOutLines(){
   
  String receivedWord;
  int wordSize;
  
    if (newData == true){
     newData = false;
     receivedWord = getWords();
     wordSize = (receivedWord.length());
     if (wordSize < charRemaining){
         lcd.print(receivedWord);
         lcd.print(' ');
         charRemaining = charRemaining - wordSize;
         charRemaining--;
      }else{
        if (lineOne == true && lineTwo == false){
          charRemaining = 16;
          lcd.setCursor(0,1);
          lcd.print(receivedWord);
          lcd.print(' ');
          charRemaining = charRemaining - wordSize;
          charRemaining--;
          lineOne = false;
          lineTwo = true;
        }else if (lineOne == false && lineTwo == true){
          charRemaining = 16;
          delay(2000);
          lcd.clear();
          lcd.setCursor(0,0);
          lcd.print(receivedWord);
          lcd.print(' ');
          charRemaining = charRemaining - wordSize;
          charRemaining--;
          lineOne = true;
          lineTwo = false;
        }
      }
    }
}

String getWords(){
  char * token;
  String newWord;
    
  token = strtok(receivedChars," ");
  while (token != NULL){
      newWord = token;
      token = strtok (NULL, "~");
      return newWord;
   }
}

Depending on simplicity... if you have a known list of messages, you can fill each line to the max chars and save messages as an array of lines. This gives you specific control for justification, etc.

Here's the simple example, which avoids a helper function to compute character positions. (I'm using a 4 line by 20 char display.)

// Global variable...
const String messages[][4] = {{
    "Put your message    ",
    "into lines padded   ",
    "to the total length."
  }, {
    "You can leave lines ",
    "empty."
}};

// Setup...
lcd.begin(20, 4);

// Somewhere in the loop...
lcd.clear();
int m = pickMessage();
int lines = sizeof(messages[m]) / sizeof(messages[m][0]);

for (uint8_t i=0; i < lines; i++) {
  lcd.setCursor(0, i);
  lcd.print(messages[m][i]);
}