(Solved) Serial data coming in need to organize it into data on a screen.

Good day All.

Once again I am in over my head and need help.

I have a self built EV, the current BMS is not driver friendly, what I mean by that is the readability can take your attention from the road which is not good.

The display uses some type of LCD display but it gets to small for when I am driving to easily read, I have been able to from an Arduino uno with the soft serial library get the info into the serial monitor in ascii text.

I will in the end be using a mega for this project one serial, the USB port, for debugging and updating software sketches, one for the nextion 4.3" screen (serial 1), the other for a (0.92"oled) hud to display speed (serial 2), and the 3rd for the serial data input from the BMS (serial 3). So thereby using all hardware serial ports on the mega..

Hope I am clear till now.

The data to be displayed will be battery capacity, Battery pack voltag, And Current (Amps).

At this point this is the data that is incoming:
Z337,337,337,338,337,337,338,337,337,337,337,337,337,338,338,337,337,337,337,337,337,337,337,337,337,337,337,337,336,337,336,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337;

The Letter Z at the start is german "Zelle" which stands for cells these are the individual cell voltages, what I need to do with the Arduino is calculate all the cells to get a pack voltage and display THAT in it designated place on the screen. This will also once calculated need to be divided by 100 to give a two place decimal value.

T-20,-20,-20,-20,-20; This is temperature but there are no sensors so this can be ignored but will need to know how to tell the arduino not to read this as I do not use it.

S-0000; The S stands for "Spannung" which is Current it shows a (-) when drawing from the pack and a (+) when charging this will also need to be displayed in the correct place on the display.

E-0000; E stands for "Energie" which is the amp hours of the battery this will be displayed not in a number but a battery picture such as on a laptop or cell phone This too has a (-) when discharging and a (+) when charging.

The cell voltages are separated by a comma , and the different infos by a semicolon ; after the
E-0000; there is also a semicolon and the serial monitor then goes to the next line and the process repeats itself, the Baud rate is stable on the Arduino uno at 9600.

I will be showing the Kw that will be calculated from the pack voltage and current being drawn or charged, this to has a location in which it will be displayed.

I need help with all this, and I will be very sure to give you a thumbs up on my channel when publishing this in video form.....

If there is anything I have left out PLEASE let me know.

In advance a big thank you!

Kind regards
Charles Gibbs

Create a char array as a buffer big enough for the longest string you will receive.

Read and throw away characters until you see one of the prefix characters.

Read and store characters in the buffer until you see a semi-colon, checking as you do that you haven't got more than you can store. If you have, start again from the beginning.

Use a switch statement based on the prefix in the buffer to decide which data you have and call a separate function for each prefix that knows how to process that string.

:slight_smile: Hi WildBill.

Um Thanks, That sounds easy enough. :frowning: :o

I am just not familiar enough with coding so that I can do it myself.

Is there any code you may be able to offer, I am a monkey see monkey do person.

Not the brightest tool in the shed I am afraid :slight_smile: :slight_smile: :slight_smile: :slight_smile:

Again thank you for a very speedy reply.

Regards

Charles

This should get you started:

const unsigned int BufferSize=230;

char buffer[BufferSize];

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

void loop()
{
static bool Storing=false;
static unsigned int Index=0;

if(Serial.available())
  {
  char ch=Serial.read();  
  if(Storing)
    {
    if(Index<BufferSize-1)
      {
      buffer[Index++]=ch;
      buffer[Index]=0;
      }
    else
      {
      Serial.println("Overflow");
      Storing=false;
      Index=0;          
      }
    if(Storing && ch==';')
      {
      ProcessBuffer();  
      Storing=false;
      Index=0;          
      }
    }
  else
    {
    if(strchr("ZETS",ch))
      {
      Storing=true;
      Index=0;
      buffer[Index++]=ch;
      buffer[Index]=0;   
      }
    }
  }
}

void ProcessBuffer()
{
Serial.println(buffer);
switch(buffer[0])
  {
  case 'Z':
    ProcessCells();
    break;
  case 'E':
    ProcessEnergy();
    break;
  case 'T':  //do nothing
    break;
  case 'S':
    ProcessCurrent();
    break;
  }
}

void     ProcessCells()
{
Serial.println("process cells");  
}

void ProcessEnergy()
{
Serial.println("process energy");  
}

void  ProcessCurrent()
{
Serial.println("process current");    
}

HI WildBill.

Cool, Thank you.

Can you explain to me what I am doing with the code at the different stages?

Just so that I may be able to learn and understand?

Thank you Again.
Karma added! :slight_smile:

Regards
Charles

Pretty much as I described above.

buffer is set to a size a bit bigger that the longest string in your examples.

Loop tries to fill that buffer with a string that starts with one of your prefix letters and ends with a semi-colon but doesn't overflow the buffer.

It uses two variables to help - storing indicates whether we already saw a valid prefix character and should be accumulating characters in the buffer. Index indicates where the next character should go in that buffer. They are both static so that they persist between calls to loop.

loop works in two modes, controlled by the storing variable. Initially, it's reading characters from serial and throwing them away until it sees one of these: "ZETS"

Then each one it reads goes to the buffer, unless there is overflow in which case we start over.

Once a semicolon is read, loop calls ProcessBuffer to deal with the string that's been accumulated. It checks the first character in the buffer (the prefix) to tell what data was received and calls a different function for each type or does nothing in the case of 'T'.

ProcessCells, ProcessEnergy and ProcessCurrent are just stubs you'll need to fill in to actually process the contents of buffer into something you can use for display.

Hi WildBill.

Ok, I'm going to have a go at it and will revert back.

Again thank you!

Regards
Charles

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data. There is also a parse example to illustrate how to extract numbers from the received text.

...R

HI Robin!

I'll look into it.

Thank you for letting me know.

My head is a bit "flat" at the best of times.

Again, Thank you.

Robin2:
Have a look at the examples in Serial Input Basics - simple reliable ways to receive data. There is also a parse example to illustrate how to extract numbers from the received text.

...R

@ Robin.

Hi Robin.

I have read through your post and examples. Very detailed and thank you for the effort you've put into the post.

It seems that example 5 will be the closest match to the code I would need.

In my OP I broke the various elements within the data down. When it comes into the serial monitor it comes in as follows.

From here: Z338,338,338,338,338,338,339,338,338,338,338,338,339,339,339,338,338,338,338,338,338,339,338,338,339,338,338,338,337,339,338,339,338,338,338,338,338,338,338,338,338,339,338,338,338,338,338,338,338,339,338,338,338,339;T-20,-20,-20,-20,-20;S+0105;E-0051; To here is ALL in one line.

After the last semicolon the serial monitor goes to the next line.

The first bit with the 'Z' and all the numbers between it and the semicolon before the 'T' need to be add together to display a total that needs to be divided by 100 to display a 2 place decimal holder value.

Each bit of info that needs to be displayed is separated by a semicolon and the last semicolon indicates the end of the received string and then the new line starts all over again.

How do I tell the Arduino to add all the numbers together and to not stop reading after that first semicolon?

There are 252 characters in that line and this is a consistent number of characters.

Again, Thank you for your detailed post on serial input basics.

Regards
Charles

Charles_Gibbs:
How do I tell the Arduino to add all the numbers together and to not stop reading after that first semicolon?

You need to post your program and tell us in detail what it actually does and what you want it to do that is different.

...R

Hi Robin.

I have no code yet as I am still trying to work out how this all should work.

I'll revert back once I may have something working.

Thank you.

@ Robin2

Hi Robin,

I am getting into the nitty gritty now and am starting to get into the code.

I am NOT going to be needing to calculate all my battery cells,There is just too much involved for my flat head to get around right now.

I'll be using a voltage divider to get my pack voltage directly from an analogue input for this purpose.

What I still do need is, to read the string into the Arduino on serial1 using YOUR code receive with start and end markers.

I will be using the last bit in the string from the BMS "E-0062;" as an example I'll have the start marker as the "E" this means that the Arduino has to interoperate the "-0062" and the end marker will be ";" whereupon the BMS starts sending new data. The - changes to a + when charging.

My question. (OH thank you for the code end explanation thereof)

I am receiving a string if I am correct?

Please correct me if not, I have a progress bar that will display the capacity of the battery, the progress bar goes from 0 to 100 in its value coulomb this is what it understands is an input of 0 to 100, how do I convert the string to a number?

Can I use the toInt() function ?

If so can you give me an example?

do I need to declare anything before hand?

Or can I just remap the value using the map function?

Sorry for all the questions.

Again thank you

Charles_Gibbs:
From here: Z338,338,338,338,338,338,339,338,338,338,338,338,339,339,339,338,338,338,338,338,338,339,338,338,339,338,338,338,337,339,338,339,338,338,338,338,338,338,338,338,338,339,338,338,338,338,338,338,338,339,338,338,338,339;T-20,-20,-20,-20,-20;S+0105;E-0051; To here is ALL in one line.

1. Check that Z has arrived;
2. after that read and save all the characters in arrayZ[] until T is found;
3. after that read and save all the characters in arrayT until S is found;
4. after that read and save all the characters in arrayS[] until E is found;
5. after that read and save all the characters in arrayE[] until ; is found.

You can use the following method for the above tasks:

byte m = Serial.readBytesUntil(terminatingCharacter, arrayName, arrayLength);

You can use the following method to convert a received data item into integer value:

interger x = atoi(arrayMember[n]);

Hi GolamMostafa.

Ok thank you, but as I am a noob when it comes to the code, could you brake it down for me to understand in laymen's terms please.

interger x = atoi(arrayMember[n]);

just this bit please.

sorry I am very new to all this.

Apologies.

Charles_Gibbs:
My question. (OH thank you for the code end explanation thereof)

I am receiving a string if I am correct?

Please correct me if not, I have a progress bar that will display the capacity of the battery, the progress bar goes from 0 to 100 in its value coulomb this is what it understands is an input of 0 to 100, how do I convert the string to a number?

Can I use the toInt() function ?

As you have not posted the code that is receiving the "string" I can't say what data type it is. If you are using one of the examples in Serial Input Basics then the variable receivedChars[] will contain a cstring - an array of chars terminated with a NULL ('\0')

The parse example in that tutorial shows how to convert a cstring to a number. For an int the appropriate function is atoi() which is short for ascii-to-integer.

It is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. This can happen after the program has been running perfectly for some time.

...R

Charles_Gibbs:
Ok thank you, but as I am a noob when it comes to the code, could you brake it down for me to understand in laymen's terms please.

interger x = atoi(arrayMember[n]);

just this bit please.

Take a simple case like this:

1. You have a character type array with five members like this:

char myArray[] = {0x31, 0x32, 0x33, 0x34, 0x00};

2. The first four members are the ASCII codes for the digits: 1, 2, 3, and 4 (Fig-1). The last member (0x00) is called the null-byte which must be there for a character type array.


Figure-1:

3. We want to retrieve the integer value 1234 from the ASCII codes of the array of Step-1. This can be done using this ready-made function: atoi(arrayName); where, atoi stands for ASCII-to-Integer.

4. The test sketch is:

char myArray[] = {0x31, 0x32, 0x33, 0x34, 0x00};

void setup()
{
   Serial.begin(9600);
   int x = atoi(myArray);
   Serial.print(x, DEC);      //shows: 1234
}

void loop()
{

}

Hi Robin2.

Ok.

I have Used your code from example #3.

IT WORKS FLAWLESSLY!!!!!

I do not know why others can't seem to get it working.

What I have done is set up and Arduino Uno with Software serial.h

and have used it to send that long string to an Arduino Mega.

The Mega in its serial monitor receives exactly the part of the text that I want. ( eg -0018)

and that is ALL, exactly as intended.

My problem:

I can't get it working on my nextion screen that I am using a progress bar on, to animate battery capacity full to empty.

I've tried using the map function but that gives me a completely WRONG number in the serial monitor where without the map function it gives the correct reading.

The nextion screen does nothing. j2 is the progress bars name and I'm trying to change the .val part of it
so that it can animate, no luck, from the BMS a reading of -0000 is full and -0030 will be empty, that is why the map function looks a bit "funky" so -0000 should be 100 on the screen and -0030 must be 0.

My, (your code) that I have adapted to my needs, as follows.

// Example 3 - Receive with start- and end-markers

const byte numChars = 32;
char receivedChars[numChars];
int AHBarValue; // this variable stores the value for the battery Capacity
boolean newData = false;

void setup() {
    Serial.begin(9600);
    Serial1.begin(9600);
    Serial2.begin(9600);
    Serial.println("<Arduino is ready>");
}

void loop() {
    recvWithStartEndMarkers();
    showNewData();
}

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = 'E';
    char endMarker = ';';
    char rc;
 
    while (Serial1.available() > 0 && newData == false) {
        rc = Serial1.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

void showNewData() {
    if (newData == true) {
       // Serial.print("This just in ... ");
        AHBarValue = map (receivedChars, -0000, -0030, 100, 0); //Something is not right here I think, 
        Serial.println(AHBarValue); // When I use this AHBarValue I get a 2450 returned in the serial 
                                                //  monitor
         // Serial.println(receivedChars); // This works perfectly and returns the value of -0018
        //AHBarValue = map (receivedChars, -0000, -0030, 100, 0);
        Serial2.print ("j2.val=");
        Serial2.print (AHBarValue);
        Serial2.print (0xff);
        Serial2.print (0xff);
        Serial2.print (0xff);
        newData = false;
    }
}

I have to have screwed something up somewhere.

Can you help me please?

Thank you once again

Regards
Charles

AHBarValue = map (receivedChars, -0000, -0030, 100, 0); //Something is not right here I think,

You are quite right.

You have to convert the value to an int using atoi() before you can use map

Something like

newVal = atoi(receivedChars);
AHBarValue = map (newVal, 0, -30, 100, 0);

...R

@ Robin2

:slight_smile: :slight_smile: :slight_smile: :slight_smile: :slight_smile: :slight_smile: :slight_smile: :slight_smile:

I told you I screwed up something.....

I'll try that and revert back..

Thanks Robin2

Regards
Charles