Reading serial data from PC

Hi.
I'm working on a Euro Truck Simulator 2 real dashboard, and I want to use the code that Silas Parker already published and modify it to my special needs (for instance, adding fuel, oil, water... gauges, which the telemetry plugin provides.
The below is Silas' original code (only modified to work with I2C lcd), and the last part of the code provides info to a 16x2 LCD screen, specifically MPH, KPH, gear and fuel. I find it hard to reverse-engineer how to get specific values of the lcd (such as the fuel level) and other values that are not printed in the lcd, but I know the plugin provides. I've tried printing the serial data into the serial monitor, but as soon as you add a Serial.print command, it seems to break everything and then neither the serial monitor nor the lcd display anything (I'm assuming it's messing with the serial connection somehow).
This is how the LCD looks like when it's working:

/*
Copyright (c) 2013, Silas Parker
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    Redistributions of source code must retain the above copyright notice, this
    list of conditions and the following disclaimer.
    Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
    The name of Silas Parker may not be used to endorse or promote products
    derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <LiquidCrystal_I2C.h>
#include <Servo.h>

const int SPEEDO_PIN      = A1;
const int RPM_PIN         = A0;
const int LEFT_INDICATOR  = A2;
const int RIGHT_INDICATOR = A3;
const int PARKING_BREAK   = A4;
const int FUEL_WARNING    = A5;

// Servo variables
Servo speedo;
Servo rpm;



#define PACKET_SYNC 0xFF
#define PACKET_VER  2

#define SERVO_DIR_NORMAL false
#define SERVO_DIR_INVERT true

int serial_byte;

LiquidCrystal_I2C lcd(0x27,20,4);

void setup()
{
  Serial.begin(115200);
  
  lcd.init();
  lcd.backlight();
  lcd.print("Self Test");
  
  // Initialise servos
  speedo.attach(SPEEDO_PIN);
  speedo.write(180);
  
  rpm.attach(RPM_PIN);
  rpm.write(180);
  
  // Initialise LEDs
  pinMode(LEFT_INDICATOR, OUTPUT);
  pinMode(RIGHT_INDICATOR, OUTPUT);
  pinMode(PARKING_BREAK, OUTPUT);
  pinMode(FUEL_WARNING, OUTPUT);
  
  digitalWrite(LEFT_INDICATOR, 0);
  digitalWrite(RIGHT_INDICATOR, 0);
  digitalWrite(PARKING_BREAK, 0);
  digitalWrite(FUEL_WARNING, 0);
  
  
  delay(500);
  
  speedo.write(0);
  rpm.write(0);
  digitalWrite(LEFT_INDICATOR, 1);
  digitalWrite(RIGHT_INDICATOR, 1);
  digitalWrite(PARKING_BREAK, 1);
  digitalWrite(FUEL_WARNING, 1);
  
  
  delay(500);
  
  speedo.write(180);
  rpm.write(180);
  digitalWrite(LEFT_INDICATOR, 0);
  digitalWrite(RIGHT_INDICATOR, 0);
  digitalWrite(PARKING_BREAK, 0);
  digitalWrite(FUEL_WARNING, 0);
  
  
  lcd.clear();
  lcd.print("Wait");
  
  // Wait a second to ensure serial data isn't from re-programming 
  delay(1000);
  lcd.clear();
  lcd.print("Ready");
}



void read_serial_byte_set_servo(Servo& servo, bool invert)
{
  serial_byte = Serial.read();
  serial_byte = (serial_byte < 0) ? 0 : ((serial_byte > 180) ? 180 : serial_byte);
  if (invert)
    servo.write(180 - serial_byte);
  else
    servo.write(serial_byte);
}

void skip_serial_byte()
{
  (void)Serial.read();
}

void digitalWriteFromBit(int port, int value, int shift)
{
  digitalWrite(port, (value >> shift) & 0x01);
}

void loop()
{
  if (Serial.available() < 16)
    return;
  
  serial_byte = Serial.read();
  if (serial_byte != PACKET_SYNC)
    return;
    
  serial_byte = Serial.read();
  if (serial_byte != PACKET_VER)
  {
    lcd.clear();
    lcd.print("PROTOCOL VERSION ERROR");
    return;
  }
  
  read_serial_byte_set_servo(speedo, SERVO_DIR_INVERT); // Speed  
  read_serial_byte_set_servo(rpm, SERVO_DIR_INVERT); // RPM
  
  skip_serial_byte(); // Brake air pressure
  skip_serial_byte(); // Brake temperature
  skip_serial_byte(); // Fuel ratio
  skip_serial_byte(); // Oil pressure
  skip_serial_byte(); // Oil temperature
  skip_serial_byte(); // Water temperature
  skip_serial_byte(); // Battery voltage
    
  
  // Truck lights byte
  serial_byte = Serial.read();
  digitalWriteFromBit(LEFT_INDICATOR,  serial_byte, 5);  
  digitalWriteFromBit(RIGHT_INDICATOR, serial_byte, 4);
  
  // Warning lights bytes

  serial_byte = Serial.read();  
  digitalWriteFromBit(PARKING_BREAK, serial_byte, 7);
  digitalWriteFromBit(FUEL_WARNING, serial_byte, 3);  
 
  // Enabled flags
  serial_byte = Serial.read();
  
  // Text length
  int text_len = Serial.read();
  
  // Followed by text
  if (0 < text_len && text_len < 127)
  {
    lcd.clear();
    for (int i = 0; i < text_len; ++i)
    {
      while (Serial.available() == 0) // Wait for data if slow
      {
        delay(2);
      }
      serial_byte = Serial.read();
      if (serial_byte < 0 && serial_byte > 127)
        return;
      
      if (serial_byte == '\n')
      {
        lcd.setCursor(0, 1);
      }
      else
        lcd.print(char(serial_byte));
//      delay(2);
    }
  }
  
}

Start with a simple test

The code that you posted skips several byes in the incoming Serial data in the loop() function. Let's see if you can read a couple of them

Change part of the code in loop() to this and leave the rest alone

  skip_serial_byte(); // Brake temperature
  int fuelRatio = Serial.read(); // Fuel ratio
  Serial.print("Fuel ratio : ");
  Serial.println(fuelRatio);
  int oilPressure = Serial.read(); // Oil pressure
  Serial.print("Oil pressure : ");
  Serial.println(oilPressure);
  skip_serial_byte(); // Oil temperature

What is printed in the Serial monitor ?

Hi @UKHeliBob . As I said, the serial monitor seems to break things somehow, because it shows nothing on the monitor nor the lcd, so I've slightly modified your code to display the values in the lcd:

  skip_serial_byte(); // Brake air pressure
  skip_serial_byte(); // Brake temperature
  int fuelRatio = Serial.read(); // Fuel ratio
  lcd.setCursor(0,0);
  lcd.print("F: ");
  lcd.setCursor(3,0);
  lcd.print(fuelRatio);
  int oilPressure = Serial.read(); // Oil pressure
  lcd.setCursor(0,1);
  lcd.print("Oil: ");
  lcd.setCursor(5,0);
  lcd.print(oilPressure);
  skip_serial_byte(); // Oil temperature
  skip_serial_byte(); // Water temperature
  skip_serial_byte(); // Battery voltage

On fuel, it displays a 4 digit value. Seems to be like 54.52% but without the dot or the percentage symbol. So that's fine, I can work with that.
On oil, it displays nothing, unfortunately. I'll keep doing some more tests, to see if I can get a numerical number for speed, rpm, water temperature...
Thank you

Nevermind, I've seen the problem now. I've messed up with the lcd cursor. I think I'll be able to extract the values properly from there.

The values read from Serial in the sketch are bytes. They will never be greater than 255 nor will they be floats with a decimal place

I suggest that we work out why using the Serial monitor is not working before going any further. Have you got anything connected to pins 0 and 1 ?

Come to that, which Arduino board are you using ?

I'm using an Arduino Mega 2560.
The only pins I'm using at this point are 5V, GND, SCA and SCL for the LCD.
Also, I'm getting values higher than 255. I've just tested speed and RPM and I'm getting the following values (which btw, don't fully match the dashboard in game :frowning: )
This is what the lcd shows:


and this is the dash in the game:

The RPM go up to 999 at some point (not 999 RPM in the dashboard), and after that, it seems to start again from that point, that's why you see such a small number in the lcd. It's about 990 in the LCD when ingame is about to reach 15 (I guess 1500? 15000 seems too much)

You will not be getting values greater than 255 in each byte read from Serial. However, the code does this

  // Text length
  int text_len = Serial.read();
  // Followed by text
  if (0 < text_len && text_len < 127)
  {
    lcd.clear();
    for (int i = 0; i < text_len; ++i)
    {
      while (Serial.available() == 0) // Wait for data if slow
      {
        delay(2);
      }
      serial_byte = Serial.read();
      if (serial_byte < 0 && serial_byte > 127)
        return;
      if (serial_byte == '\n')
      {
        lcd.setCursor(0, 1);
      }
      else
        lcd.print(char(serial_byte));
      //      delay(2);
    }

What that section of code does is to read a byte that tells it how many bytes of text are in the message immediately following the length byte. Then it reads that many bytes, treats each one as a character and prints it to the LCD. Judging by what I can see the text also includes the labels such as "MPH" and "KPH"

So, strictly you are receiving text representing a number rather than the number itself. That is not a problem if all you want to do is print it

Personally I would start by writing every byte received to the Serial monitor if its value is between 32 ( a space) and 126 (a tilde). That way you will be able to see what you are receiving

I can't do that, the Serial monitor is not working. Maybe it has something to do with the baud rate? I've tried 115200 and 9600 and they do nothing.

Start by printing a simple message in setup(). The baud rate is set to 115200 in the sketch. What is it set to in the Serial monitor ?

Ok, so if I do this, it works as expected:

void setup() {
  Serial.begin(9600);
  Serial.print("Hello world");
}

void loop() {

}

If I add the serial print to the code above, right after the Serial.begin, and update the baud rate in the serial monitor screen to 115200, it does nothing. It does print the messages in the lcd, though, so the setup does actually run.
Without that line, as soon as I start the game, the lcd displays MPH, KPH, etc. With that line, it doesn't work. That's why I thought the Serial.print is missing with the serial communication somehow.

nevermind, it does work. Seems to be a bit unstable though. How would you print every single byte?

I think the problem is having the serial monitor open. If I load the normal sketch (without the Serial.print commands), and try to open the serial monitor while in game, I get this:
image

The problem is being caused by using the Serial interface to receive data from the game and also to send data to the Serial monitor

Which Arduino board are you using and do you have any others ?

I'm using a Mega, but I do have also an UNO and some nanos

Can you configure the baud rate the game uses?

The Mega has several Serial interfaces so would be a good choice or you could use SoftwareSerial on the Uno or Nano, but do you have a USB to serial converter so that you can have 2 serial interfaces to the PC ?

If not then a different strategy will be required for debugging by using the LCD to view the raw received data unless you have any other type of display capable of being driven by one of the Arduinos

The worst, but maybe only other option would be to fly blind and grab the serial data, which is quite well commented in the code, and put it on the LCD

You mean a USBasp? becuase if so, I do actually have one.

No. I was wondering whether you had a TTL to USB serial connector so that you could create a second serial interface from the Mega to the PC to send the received messages to a second Serial monitor on the PC.

Not to worry. Let's do a little bit of blind flying

I will make a change to the sketch to display just the oil temperature on the LCD and if that works we can go from there.

Which data do you actually want to display on the LCD ?

Try this and let me know what happens, if anything

/*
Copyright (c) 2013, Silas Parker
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    Redistributions of source code must retain the above copyright notice, this
    list of conditions and the following disclaimer.
    Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
    The name of Silas Parker may not be used to endorse or promote products
    derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <LiquidCrystal_I2C.h>
#include <Servo.h>

const int SPEEDO_PIN      = A1;
const int RPM_PIN         = A0;
const int LEFT_INDICATOR  = A2;
const int RIGHT_INDICATOR = A3;
const int PARKING_BREAK   = A4;
const int FUEL_WARNING    = A5;

// Servo variables
Servo speedo;
Servo rpm;



#define PACKET_SYNC 0xFF
#define PACKET_VER  2

#define SERVO_DIR_NORMAL false
#define SERVO_DIR_INVERT true

int serial_byte;

LiquidCrystal_I2C lcd(0x27, 20, 4);

void setup()
{
  Serial.begin(115200);
  lcd.init();
  lcd.begin(16, 2);     //IGNORE THIS LINE
  lcd.backlight();
  lcd.print("Self Test");
  // Initialise servos
  speedo.attach(SPEEDO_PIN);
  speedo.write(180);
  rpm.attach(RPM_PIN);
  rpm.write(180);
  // Initialise LEDs
  pinMode(LEFT_INDICATOR, OUTPUT);
  pinMode(RIGHT_INDICATOR, OUTPUT);
  pinMode(PARKING_BREAK, OUTPUT);
  pinMode(FUEL_WARNING, OUTPUT);
  digitalWrite(LEFT_INDICATOR, 0);
  digitalWrite(RIGHT_INDICATOR, 0);
  digitalWrite(PARKING_BREAK, 0);
  digitalWrite(FUEL_WARNING, 0);
  delay(500);
  speedo.write(0);
  rpm.write(0);
  digitalWrite(LEFT_INDICATOR, 1);
  digitalWrite(RIGHT_INDICATOR, 1);
  digitalWrite(PARKING_BREAK, 1);
  digitalWrite(FUEL_WARNING, 1);
  delay(500);
  speedo.write(180);
  rpm.write(180);
  digitalWrite(LEFT_INDICATOR, 0);
  digitalWrite(RIGHT_INDICATOR, 0);
  digitalWrite(PARKING_BREAK, 0);
  digitalWrite(FUEL_WARNING, 0);
  lcd.clear();
  lcd.print("Wait");
  // Wait a second to ensure serial data isn't from re-programming
  delay(1000);
  lcd.clear();
  lcd.print("Ready");
}



void read_serial_byte_set_servo(Servo& servo, bool invert)
{
  serial_byte = Serial.read();
  serial_byte = (serial_byte < 0) ? 0 : ((serial_byte > 180) ? 180 : serial_byte);
  if (invert)
    servo.write(180 - serial_byte);
  else
    servo.write(serial_byte);
}

void skip_serial_byte()
{
  (void)Serial.read();
}

void digitalWriteFromBit(int port, int value, int shift)
{
  digitalWrite(port, (value >> shift) & 0x01);
}

void loop()
{
  if (Serial.available() < 16)
    return;
  serial_byte = Serial.read();
  if (serial_byte != PACKET_SYNC)
    return;
  serial_byte = Serial.read();
  if (serial_byte != PACKET_VER)
  {
    lcd.clear();
    lcd.print("PROTOCOL VERSION ERROR");
    return;
  }
  read_serial_byte_set_servo(speedo, SERVO_DIR_INVERT); // Speed
  read_serial_byte_set_servo(rpm, SERVO_DIR_INVERT); // RPM
  skip_serial_byte(); // Brake air pressure
  skip_serial_byte(); // Brake temperature
  skip_serial_byte(); // Fuel ratio
  skip_serial_byte(); // Oil pressure
  byte oilTemp = Serial.read(); // Oil temperature
  skip_serial_byte(); // Water temperature
  skip_serial_byte(); // Battery voltage
  // Truck lights byte
  serial_byte = Serial.read();
  digitalWriteFromBit(LEFT_INDICATOR,  serial_byte, 5);
  digitalWriteFromBit(RIGHT_INDICATOR, serial_byte, 4);
  // Warning lights bytes
  serial_byte = Serial.read();
  digitalWriteFromBit(PARKING_BREAK, serial_byte, 7);
  digitalWriteFromBit(FUEL_WARNING, serial_byte, 3);
  // Enabled flags
  serial_byte = Serial.read();
  // Text length
  int text_len = Serial.read();
  // Followed by text
  if (0 < text_len && text_len < 127)
    //  {
    //    lcd.clear();
    //    for (int i = 0; i < text_len; ++i)
    //    {
    //      while (Serial.available() == 0) // Wait for data if slow
    //      {
    //        delay(2);
    //      }
    //      serial_byte = Serial.read();
    //      if (serial_byte < 0 && serial_byte > 127)
    //        return;
    //      if (serial_byte == '\n')
    //      {
    //        lcd.setCursor(0, 1);
    //      }
    //      else
    //        lcd.print(char(serial_byte));
    //      //      delay(2);
    //    }
    //  }
    lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Oil temp : ");
  lcd.print(oilTemp);
}

If it works then I will explain how !
If not then maybe I can fix it


But I think that part was already working. The fuel, oil temp, oil pressure... that block of information is clear. The part I'm having troubles with is the speed, rpm, and also I would like to get other data that I think is available but not used in the sketch.