Hello, I have a program that uses serial reads to take commands from a computer and a switch case to have different commands do different actions. This structure has worked great for most things, however, I am now trying to add a function that if you pass a certain command it will again start waiting for a serial input then read that new command and do something then go back to the initial state.

Specifically this is for a smart battery system using SMBus/I2C where i want to both be able to command it to pull several registers to report them out to the user, but the new command is to program a new serial number in, programming of the serial number into the pack works fine but it had to be hard coded in, i wanted to add a pause and wait for serial to receive the serial number to be programmed by the operator.

The issue i ran into, it pauses without issue but if i include a serial.readbytesuntil in the program switch case it seems to always trigger even if that case is not what is the switch selected.

Here is the Code:

//Include Wire Library
#include <Wire.h>

//Address of Battery
#define Batt_Address 11

//Battery registers 
#define temp      0x08
#define volt      0x09
#define curr      0x0A
#define error     0x0C
#define fcc       0x10
#define cycle     0x17
#define chgCurr   0x14
#define chgVolt   0x15
#define desCap    0x18
#define desVolt   0x19
#define serial    0x1C
#define cell1     0x38
#define cell2     0x39
#define cell3     0x3A
#define cell4     0x3B
#define cell5     0x3C
#define cell6     0x3D
#define cell7     0x3E
#define cell8     0x3F
#define MFGACCESS      0x44
#define MFGSERIAL1     0x6D
#define MFGSERIAL2     0x40
#define MFGSERIAL3     0xFF
#define MFGSERIAL4     0xFF
byte MFGSERIAL [] = {0x44, 0x04, 0x6D, 0x40, 0x34, 0xFF};

char chararr[30];
int bufferSize = 0;

void setup() {
  // put your setup code here, to run once:

Serial.begin(115200);
Wire.begin();
Wire.setClock(100000);
}

int fetchWord(byte func)
{
  uint8_t vals[2] = {0,0};
  uint8_t count = 0;
  Wire.beginTransmission(Batt_Address);
  Wire.write(func);
  Wire.endTransmission();
  Wire.requestFrom(Batt_Address, 2);
      while(Wire.available())
      {
        vals[count++] = Wire.read();
      }
      return (int)(vals[1]) << 8 | (int)(vals[0]);
}

int fetchWord1(byte func)
{
  uint8_t vals[2] = {0,0};
  uint8_t count = 0;
  Wire.beginTransmission(Batt_Address);
  Wire.write(func);
  Wire.endTransmission();
  Wire.requestFrom(Batt_Address, 2);
      while(Wire.available())
      {
        vals[count++] = Wire.read();
      }
      return (int)(vals[1]) << 8 | (int)(vals[0]);
}


void writeSerial(byte func[])
{
  Serial.print(func[1]);
  Wire.beginTransmission(Batt_Address);
  Wire.write(68);
  Wire.write(109);
  Wire.write(64);
  Wire.write(255);
  Wire.write(255);
  Wire.endTransmission();
}

void loop() {
  while(Serial.available()>0){;
    delay(1);
    bufferSize = Serial.available();
    Serial.readBytesUntil('\n',chararr,bufferSize);
    if (isLowerCase(chararr[0])){  
      chararr[0] = chararr[0] -32;    //shift lowercase to uppercase
    }
    switch (chararr[0]){
      case 'P':{
        for(int i=0; i<=30; i++){
          chararr[i] = '\0';
        }
        Serial.println("Enter Serial Number");
        while(!Serial.available()){;
        }
        delay(1);
        bufferSize = Serial.available();
        Serial.readBytesUntil('\n',chararr,bufferSize); // <-- This is the line that seems to cause issue
        if (isLowerCase(chararr[0])){  
          chararr[0] = chararr[0] -32;    //shift lowercase to uppercase
        }
        Serial.println(chararr);
        // Wire.beginTransmission(Batt_Address);
        // Wire.write(MFGSERIAL,6);
        // Wire.endTransmission();
        for(int i=0; i<=30; i++){
          chararr[i] = '\0';
        }
        Serial.flush(); 
        break;
      }
      case 'A':{
        Serial.print("Serial Number: ");
        Serial.println((unsigned)fetchWord(serial));

        Serial.print("Pack Voltage: ");
        Serial.println((unsigned)fetchWord(volt));

        Serial.print("Cell 1 Voltage: ");
        Serial.println(fetchWord(cell1));

        Serial.print("Cell 2 Voltage: ");
        Serial.println(fetchWord(cell2));

        Serial.print("Cell 3 Voltage: ");
        Serial.println(fetchWord(cell3));

        Serial.print("Cell 4 Voltage: ");
        Serial.println(fetchWord(cell4));

        Serial.print("Cell 5 Voltage: ");
        Serial.println(fetchWord(cell5));

        Serial.print("Cell 6 Voltage: ");
        Serial.println(fetchWord(cell6));

        Serial.print("Cell 7 Voltage: ");
        Serial.println(fetchWord(cell7));

        Serial.print("Cell 8 Voltage: ");
        Serial.println(fetchWord(cell8));

        Serial.print("Pack Temp: ");
        Serial.println(fetchWord(temp));

        Serial.print("Pack Current: ");
        Serial.println(fetchWord(curr));

        Serial.print("Charging Voltage: ");
        Serial.println((unsigned)fetchWord(chgVolt));

        Serial.print("Charging Current: ");
        Serial.println(fetchWord(chgCurr));

        Serial.print("Design Voltage: ");
        Serial.println(fetchWord(desVolt));

        Serial.print("Design Capacity: ");
        Serial.println(fetchWord(desCap));

        Serial.print("Cycle Count: ");
        Serial.println(fetchWord(cycle));

        Serial.print("Max Error: ");
        Serial.println(fetchWord(error));
        Serial.println();
        for(int i=0; i<=30; i++){
          chararr[i] = '\0';
        }
        Serial.flush(); 
        break;
      }
      default:{
        Serial.println(chararr);
        for(int i=0; i<=30; i++){
          chararr[i] = '\0';
        }
        Serial.flush(); 
        break;
      }
    }
  }
}

If i comment out the readbytesuntil then all other parts of the code seem to work perfectly.
If i leave that in then no matter what is sent and picked up by the switch case it will only work every other time serial data is passed.

So if i were to send F it will respond F because it goes to the default case, but if i send F again (or anyhting else) it does nothing, then the next time it operates normal. Same thing happens for command A, only every other time that command is sent it will activate pulling the i2c data.

My hunch is the readbytes until is triggering somehow despite not being the case that was selected and leaving an empty chararr so it does nothing. I tried also adding in the P command where it waits for serial to print a repeating ... thinking maybe that was triggering, but that only triggers if P is the command sent.

I had an issue early on in this program where the A function needed to be in {} but the P function has those.

Does anyone have info they could give on using serial read inside a switch statement?

If i break out the serial read to a separate function that the P case calls on will that stop it?

Thanks for any help y'all.
Tim

Suppose only 1 byte happens to be available here:

...and bufferSize==1. What will readBytesUntil do?

char chararr[30];

for(int i=0; i<=30; i++){
          chararr[i] = '\0';
        }

There are many places in the code where you are writing beyond the limit of the array and anything can happen.

The index numbers run from 0 to 29

for(int i=0; i<=29; i++){
          chararr[i] = '\0';
        }
1 Like

If there is only one byte then that byte is /n both with arduino serial monitor and the program i made to use with this. So it reads the one byte which is the termination character and populates chararr with nothing. I'm not following what your questions is. If I purposely try to break it I can by purposely not sending a termination /n character.

Commenting out the problematic line. i can try to show you what the section of code you highlighted does with short to empty serial data. Hard to show it replying with blank, but if I send it f then empty then f again this is the result

The section you highlighted has given no problems in several projects i have done with it. It seems to be only when adding in the second point where i try to read within the case structure (line i commented in case P) that i causes the every other serial command function.

What I'm curious to is why would a command in a case that is never run affect the other cases unless there is something weird about serial that like excludes it from case structures to where that is run regardless of the case.

Ah, I thought it could possibly be that your program was running faster than the serial link was delivering the characters and then processing incomplete/corrupt messages.

Something like this toy does:

char buffer[100];

void setup() {
  Serial.begin(9600);
  Serial.println("enter chars:");
}

void loop() {
  if (Serial.available() > 0) {
    delay(1);
    int j = Serial.available();
    Serial.readBytesUntil('\n', buffer, j);
    Serial.print(':');
    Serial.print(buffer);
    Serial.println(':');
  }
}

with "qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm\n"
delivering something like:

enter chars:
:q:
:e:
:rt:
:yt:
:ui:
:oi:
:pa:
:sa:
:df:
:gf:
:hf:
:jk:
:lk:
:zk:
:xc:
:vbnmq:
:wertyuiop:
:asdfghjklzxcv:
:bnmfghjklzxcv:

I think your bufferSize should be a constant so you can read the hwole message, and can't overwrite the end of the buffer with a 100 character long string:

const size_t bufferSize = sizeof(chararr);
1 Like

Ok I appear to have "fixed" it, If i make a separate char array for the P case then it functions as expected. But if anyone could point out why i was only getting very other reply when the P case was using the same chararr i would be very curious to learn. I just do not understand how the P case would affect the A or Default cases.

Here is the corrected code that works as intended in case anyone needs it. It is getting expanded so in the P case it will turn the sent serial number into little endian hex then it send that to a TI BQ78350 to load the serial number into the data flash. If you want the final code that does the TI data flash message me some time in like a week once it is done.

//Include Wire Library
#include <Wire.h>

//Address of Battery
#define Batt_Address 11

//Battery registers 
#define temp      0x08
#define volt      0x09
#define curr      0x0A
#define error     0x0C
#define fcc       0x10
#define cycle     0x17
#define chgCurr   0x14
#define chgVolt   0x15
#define desCap    0x18
#define desVolt   0x19
#define serial    0x1C
#define cell1     0x38
#define cell2     0x39
#define cell3     0x3A
#define cell4     0x3B
#define cell5     0x3C
#define cell6     0x3D
#define cell7     0x3E
#define cell8     0x3F
#define MFGACCESS      0x44
#define MFGSERIAL1     0x6D
#define MFGSERIAL2     0x40
#define MFGSERIAL3     0xFF
#define MFGSERIAL4     0xFF
//byte MFGSERIAL [] = {0x44, 0x04, 0x6D, 0x40, 0x34, 0xFF};
byte MFGSERIAL [] = {68, 4, 109, 64, 255, 255};

char chararr[30];
char serNum[30];
int bufferSize = 0;

void setup() {
Serial.begin(115200);
Wire.begin();
Wire.setClock(100000);
}

int fetchWord(byte func)
{
  uint8_t vals[2] = {0,0};
  uint8_t count = 0;
  Wire.beginTransmission(Batt_Address);
  Wire.write(func);
  Wire.endTransmission();
  Wire.requestFrom(Batt_Address, 2);
      while(Wire.available())
      {
        vals[count++] = Wire.read();
      }
      return (int)(vals[1]) << 8 | (int)(vals[0]);
}

void loop() {
  while(Serial.available()>0){;
    delay(1);
    bufferSize = Serial.available();
    Serial.readBytesUntil('\n',chararr,bufferSize);
    if (isLowerCase(chararr[0])){  
      chararr[0] = chararr[0] -32;    //shift lowercase to uppercase
    }
    switch (chararr[0]){
      case 'P':{
        Serial.println("Enter Serial Number");
        while(!Serial.available()){;
        }
        delay(1);
        bufferSize = Serial.available();
        Serial.readBytesUntil('\n',serNum,bufferSize);
        Serial.println(serNum);
        // Wire.beginTransmission(Batt_Address);
        // Wire.write(MFGSERIAL,6);
        // Wire.endTransmission();
        for(int i=0; i<=30; i++){
          serNum[i] = '\0';
        }
        Serial.flush(); 
        break;
      }
      case 'A':{
        Serial.print("Serial Number: ");
        Serial.println((unsigned)fetchWord(serial));

        Serial.print("Pack Voltage: ");
        Serial.println((unsigned)fetchWord(volt));

        Serial.print("Cell 1 Voltage: ");
        Serial.println(fetchWord(cell1));

        Serial.print("Cell 2 Voltage: ");
        Serial.println(fetchWord(cell2));

        Serial.print("Cell 3 Voltage: ");
        Serial.println(fetchWord(cell3));

        Serial.print("Cell 4 Voltage: ");
        Serial.println(fetchWord(cell4));

        Serial.print("Cell 5 Voltage: ");
        Serial.println(fetchWord(cell5));

        Serial.print("Cell 6 Voltage: ");
        Serial.println(fetchWord(cell6));

        Serial.print("Cell 7 Voltage: ");
        Serial.println(fetchWord(cell7));

        Serial.print("Cell 8 Voltage: ");
        Serial.println(fetchWord(cell8));

        Serial.print("Pack Temp: ");
        Serial.println(fetchWord(temp));

        Serial.print("Pack Current: ");
        Serial.println(fetchWord(curr));

        Serial.print("Charging Voltage: ");
        Serial.println((unsigned)fetchWord(chgVolt));

        Serial.print("Charging Current: ");
        Serial.println(fetchWord(chgCurr));

        Serial.print("Design Voltage: ");
        Serial.println(fetchWord(desVolt));

        Serial.print("Design Capacity: ");
        Serial.println(fetchWord(desCap));

        Serial.print("Cycle Count: ");
        Serial.println(fetchWord(cycle));

        Serial.print("Max Error: ");
        Serial.println(fetchWord(error));
        Serial.println();

        break;
      }
      default:{
        Serial.println(chararr);
        break;
      }
    }
    for(int i=0; i<=30; i++){
      chararr[i] = '\0';
    }
    Serial.flush(); 
  }
}

Thanks Y'all

Not at all. What don't you understand about undefined behavior and the fact that anything can happen when you access or write an array beyond its bounds. Fix this error in the original program and it will probably work as intended.

char chararr[30];
char serNum[30];
for(int i=0; i<=30; i++){
          serNum[i] = '\0';
        }

 for(int i=0; i<=30; i++){
      chararr[i] = '\0';
    }
2 Likes

Hello thennesy

Check out and test the SerialEvent example to be found in the IDE.

This a nice CLI to be extended to your needs:

https://www.norwegiancreations.com/2018/02/creating-a-command-line-interface-in-arduinos-serial-monitor/

1 Like

Technically I did fix it before because it worked as intended after the change, it is just not even remotely a clean fix.

But thanks for vaguely pointing out the syntax issue enough that i found the issue while trying to imply i do not understand that you should not over flow and array. I do not really understand why people on this forum wont just say, "hey you got your end position wrong you're overflowing the array, change it to a < instead of <="

This was i post #3

for(int i=0; i<=29; i++){
          chararr[i] = '\0';
        }

It's a matter of style if you want <= 29 or <30 They are equivalent.

Wow apparently y'all as just way too fast for me to keep up, apparently there were several posts between the first person replying and me replying to them so never saw your first one, the second post just seemed aggressive without the context of the first. Anyway thanks for pointing it out.

And neither is as clear(2 months from now, especially) as

const int chararrlen = 30;
char chararr[chararrlen];
....
for(int i = 0; i < chararrlen; i++){

Using a well named limit constant is far better than sprinkling magic numbers. Costs you nothing but a bit of typing and forethought.

As always, YMMV.

1 Like

Yes. I looked for the religious (and worse) arguments about this and only found reasonable ppl talking about it calmly. :expressionless:

From a few minutes of research, I find a preference for using '<'. I must have been taught that or forced to at some point.

I do like it, it is the idiomatic for loop

  for (int ii = 0; ii < N; ii++) {

which just goes right past even needing to think about what it is.

a7

1 Like

Still measuring that in months, eh?

I can confuse myself by nap time if I start early enough.

a7

1 Like

I think it's because there are lots of good reasons for having a named constant that happens to be the length of the item, and therefore it's consistent and useful to use that named constant in the loop, but to use it most usefully the < limit is required because, of course, index starts at 0, not 1.
YMMV.

1 Like

This is a wise suggestion.

I was speaking to the younger crowd. We're both on that same slippery slope, grandpa.

1 Like

Twice, I've been told, "but I don't want to do that, it wastes memory". Not so. The compiler takes care of that, from beginning to end. No difference from an explicit value, except that

  • you'll remember what the named constant is for,
  • you'll only have one place to change the numeric value when you decide your buffer needs to be 35 chars not 30
  • because it's named 'constant', the compiler also knows to tell you not to try changing it when you miscode something, Like,
    if(chararrlen = index)
    instead of
    if(chararrlen == index)
    This also, by the way, speaks to putting your constant first in the == comparison, instead of the very common
    if index = chararrlen), because the latter will NOT throw an error(just a warning, if you enable that level).
    but I digress.
1 Like