Simple char array wierdness

I'm wishing to get away from Strings, use char arrays. Thought I was doing simple practice & familiarization - but that was 10 hrs ago....

code & annoying output attached. Using MEGA2560 w/ IDE on a PC.

Question1: Reference manual says "Serial.available()" returns number of bytes available to read, but I always get a return value of 1. What am I missing?
Real question is below: HUH! what am I missing...

part A - entered "cows" on serial monitor and output was just as expected except for the "chars available to read = 1", nevertheless got an output from reading the array "inStrings[]" char by char of "cows". Great.

part B - entered "cowboys" but got NO output from reading "inStrings[]" char-by-char. Not Great. Then entered "bears" & got no output.

part C - then entered "bear" & got GOOD OUTPUT.

part TheEnd - "inStrings[]" is 10 slots but never get good output if there are more than 4 printable chars in it. At least that's what I think. Have done this 9 ways to Sunday before I bothered y'all.

Free beer in Dallas for any useful insights....and THANKS.
Chuck

This is simple code. Prompt to enter anything on serial monitor & put up to 9 chars in the array "inString[]" then put a NULL at their end. Reset the input location to 0 and do it again. :frowning:

// name is "sketch_Cstrings2"
// 12/01/20214
// on MEGA2560
// at home in CUBBY
#include <stdio.h>
#include <string.h>

char pgmName[] = "sketch_Cstrings2";
int i = 0,j = 0,k = 0,loopCnt = 0;
int charInCnt = 0;
int num1 = 0, num2 = 0, num3 = 0;
const int maxInLen = 10;
volatile char inString[maxInLen];
const int maxOutLen = 50;
volatile char outString[maxOutLen];
char inChar;

void setup() {
  Serial.begin(115200);
  while (!Serial) {;}
  Serial.print("pgmName: ");
  Serial.println(pgmName);
  for(i=0; i<(maxInLen-1); i++) inString[i] = ' ';
  inString[i] = '\0'; 
  for(i=0; i<(maxOutLen-1); i++)outString[i] = ' ';
  outString[i] = '\0';
  Serial.println("GOING TO LOOP.");
}// END setup()

void loop(){
  loopCnt++;
  inChar = ' ';
  charInCnt = 0;
  if(k == 0){
    Serial.println("enter a topic then press ENTER");
    k = 1;
    }//END if k
  while(Serial.available()>0){
    if(k == 1){
      num1 = Serial.available();
      Serial.print("chars to print? = "); Serial.println(num1);
      k = 2;
      }
    inChar = Serial.read();
    if(isPrintable(inChar) && (charInCnt <= maxInLen - 2)){
      inString[charInCnt] = inChar;
      charInCnt++;
      Serial.println(inChar);
    }
    else{//END if printable
      inString[charInCnt] = '\0';
      if(charInCnt >= maxInLen-1) Serial.println("inString is FULL!");
      Serial.println("next line is inString[]");
      for(i=0; i<charInCnt; i++) {Serial.write(inString[i]);}
      Serial.println();
      k = 0;
      }
  }//END while avbl
}// END loop()

Serial monitor Output:
pgmName: sketch_Cstrings2
GOING TO LOOP.
enter a topic then press ENTER
chars to print? = 1
c
o
w
s
next line is inString[]
cows
enter a topic then press ENTER
chars to print? = 1
c
o
w
b
o
y
s
next line is inString[]

enter a topic then press ENTER
chars to print? = 1
b
e
a
r
s
next line is inString[]

enter a topic then press ENTER
chars to print? = 1
b
e
a
r
next line is inString[]
bear
enter a topic then press ENTER

I'm not in the lab, but here's some spaghetti to throw against the wall.

Make the line endings setting in the serial monitor "none", or whatever it's called.

Make the char arrays a constant size way big enough, like 32. You have the space.

Here add a line:

      inString[charInCnt] = inChar;
      charInCnt++;
      inString[charInCnt] = '\0';    // pinch it

I see it looks like it gets took care of, but a good habit is to never have a char array that hasn't a null terminator. The extra step ensures it.

Lastly, available() is always one (1) I beleive because you are eating them as soon as they are in the buffer.

This all may be nonsense, but easily done and harmless.

a7

OK, I'm outta time.

Setting the line ending to "line feed" works. Here. Now.

But it's tired and I'm getting late. The code seems to work, hmmm. Line ending?

a7

Don't reset charInCnt to zero every time through loop. Instead, reset it at the very end, along with k = 0;

Does that fix it?

CPUs are a lot faster than serial ports, where the bytes (actually the bits) are coming in one by one. You've got a tight loop asking: "hey, do you have any data?" and Serial says, "actually, I just got this one b--"; and you're, "Great! How many bytes was that again?"

I rejiggered the loop to make some of this clearer

void loop() {
  switch (k) {
    case 0:
      Serial.println("enter a topic then press ENTER");
      k = 1;
    case 1:
      num1 = Serial.available();
      if (!num1) {
        return;
      }
      Serial.print("was ");
      Serial.print(num1);
      // delay(3);  // longer delay to buffer more (up to a point)
      Serial.print(", now chars to print? = ");
      Serial.println(Serial.available());
      k = 2;
    case 2:
      inChar = Serial.read();
      if (isPrintable(inChar)) {
        inString[charInCnt++] = inChar;
        Serial.println(inChar);
      } else {
        Serial.print("not printable: ");
        Serial.println(static_cast<int>(inChar));
      }
      bool eol = false;
      if (inChar == '\n') {
        if (Serial.peek() == '\r') {
          Serial.read();
          Serial.println("LF+CR");
        }
        eol = true;
      } else if (inChar == '\r') {
        if (Serial.peek() == '\n') {
          Serial.read();
          Serial.println("CR+LF");
        }
        eol = true;
      } else if (charInCnt == maxInLen - 1) {
        Serial.println("inString is FULL!");
        eol = true;
      }
      if (eol) {
        inString[charInCnt] = '\0';
        charInCnt = 0;
        Serial.println(inString);  // cannot be volatile
        k = 0;
      } else if (!Serial.available()) {
        k = 1;
      }
  }
}  // END loop()

For case 1, the act of printing stuff -- which takes time over the serial port -- allows more bytes to be buffered.

This also fixes the bug where the byte at the end of each full line is dropped ("it's printable, but doesn't fit, so never mind"). It displays the two-byte "Both NL & CR" sequence that the Serial Monitor actually uses (no surprise it's CR+LF).

And you neither need nor want the volatile qualifier for inString.

Reason I mentioned resetting charInCnt before is because what might be happening is

  • printing "chars to print?" has allowed some bytes to buffer
  • then you're in the while loop
    • k is now 2
    • you read a char
    • and print it
    • add it to inString
    • up to the 5th time if it is a line break
      • which is not printable and so you print the line so far
  • otherwise you exhaust the buffer and exit the loop, and reset charInCnt to zero
  • now each time the while loop only executes once
    • you print the character
    • assign it to inString[0]
    • then there is no more available
  • reset charInCnt
  • until there is a line break
    • when it just inString[0] = '\0'
    • and you print a blank line

This also depends on the timing of the CPU vs the serial port.

Nice.

Let me first apologize for trying to debug whilst driving. I am way past old enough to know better.

@chuck659, you had most of the elements of a reasonable sketch. The clue to focus on was the while loop, and @kenb4 has demonstrated doing without.

You might say rule one is to let the loop() do the looping.

I no longer need to "help", but since this has been in the back of what's left of my mind, I will share this pseudocode.

Pseudocode is a way to represent an algorithm without writing it in a specific programming language. Think of it in the same terms as we do a flowchart.

every loop

   if the phase is PROMPT
      set the character counter to zero
      request a topic and set the phase to SOLICIT

   if the phase is SOLICIT
      get a character
      if it is enter
         set the phase to PRINT
      otherwise
      count it and stash it in the character string

   if the phase is PRINT
      print the collected character string
      set the phase to PROMPT

Without the distarction of pesky syntax and glossing over a few details, things like when to pinch off the character array with the null character and so forth, you can see the loop logic (or write it in the first place) with a bit more ease.

@kenb4 has also added more serial printing, which is useful any time for verifying the value of key variables and ensuring they properly inform flow through the code.

In my coded version, which I will not write and might never have, I wouldn't have needed else in the three top level if statements. Ppl like to use them, and it does save a dozen or two microseconds. Sometimes else would be important, but here it just means when the phase advances, the next block executes right away.

So the pseudocode here is better realized, as @kenb4 has shown, by using a switch/case statement - any time you have a chain of if/else blocks numbering more than a few, it make sense to consider using switch/case. It is instantly recognized by readers and makes such things prettier when it can fit. Cases are controlled by constants, like

 case 1 :
//... code for this case
    brealk;

but constants can be easily given names in a few different ways, so you can write

  case PRIMPT :
//... code for this case
    brealk;

Cases can also take a range constant, viz:

case 1000 ... 1499 :

The real utility of the switch/case is when the steps are not sequential. Any case can set the variable controlling the switching to move to any other phase of the algorithm, which will come into play for you, I have no doubt, when you try for something nonlinear, particulary in implementing FSMs, or finite state machines.

You are going to love all that.

a7

look this over

  • Serial available will return a non-zero value as soon as it sees some some input.
  • better to have a larger than needed buffer, no need to initialize it
  • your code only prints "chars to print?" when k==1
  • Serial.readBytesUntil() is very handy

Input

it's easy to tell the depth of a well

output

it's easy to tell the depth of a well

void
loop (void)
{
    if (Serial.available ())  {     // is there some input
        char buf[90];
        // read input up to newline, limit # char to size of buf less 1
        int  n = Serial.readBytesUntil ('\n', buf, sizeof(buf)-1);
        buf [n] = '\0';             // terminate buf with NUL

        Serial.println (buf);
    }
}

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

and taking this to the presumed next step

Input

go 10

Output

  cmd go, val = 10

bool
getCmd (
    char *cmd,      // assumed (!!) to be reasonable size
    int  *pVal )
{
    if (Serial.available ())  {     // is there some input
        char buf[90];
        // read input up to newline, limit # char to size of buf less 1
        int  n = Serial.readBytesUntil ('\n', buf, sizeof(buf)-1);
        buf [n] = '\0';             // terminate buf with NUL

        sscanf (buf, "%s %d", cmd, pVal);
        return true;
    }

    return false;
}

void
loop (void)
{
    char cmd [10];
    int  val;

    if (getCmd (cmd, &val))  {
        Serial.print ("  cmd ");
        Serial.print (cmd);
        Serial.print (", val = ");
        Serial.println (val);
    }
}

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

Gentlemen (I think that's correct for you 3), excellent on all counts! Thanks very much, will get back to "messing" w/ char arrays once the day gig has been handled today. Will use several of these ideas and I do have better understanding of serial input. I do lots of code, but little or none of it has user interface of any kind...again, Thanks so much! Chuck in Dallas

This is an easy lazy way to do something that is sometimes appropriate:

        // read input up to newline, limit # char to size of buf less 1
        int  n = Serial.readBytesUntil ('\n', buf, sizeof(buf)-1);

The major flaw is in its two implicit assumptions.

First, that characters are arriving as fast as they can, or at least fast enough to beat the timeout… if it starves for characters, it will return before "until", something not obvious.

Something that is obvious is that it blocks. This is a sketch killer, and only if you literally want to do absolutely nothing else than wait on the user for text input, or whatever external process may be sending characters, avoid it like a plague.

If you want to be dazzling the user or chiding him or juggling something else, you kinda have to do things the hard way, which by now you may see isn't hard.

Even if you choose to use it when appropriate, it is very good to know how to do without. Knowing means I never use it, even if it wouldn't mess with the rest of what I want to be doing at the same time.

There are a few low level things like this that it is best to figure out and code for yourself, or come to an understanding of code someone else wrote, even if forever more you use a library. Button handling is one I think of off the top of my head.

It's similar Strings, getting away from them is healthy when we talking UNO and its ilk. Wrangling real character arrays is an important skill.

a7

1 Like

Well, the two not me members possibly. :wink:

a7

most of the time, a string is not sent until a terminating character is entered. This allows a backspace to edit the string on the transmit side

I knew you'd say that!

It is true that sometimes, maybe even most times, when we are typing a response to a prompt there is a collection at the terminal side, and the hole thing gets shipped out when enter or return is pressed.

But that is not always the case, it may be the receiving entity that handles all that including deleting characters typed in error.

And devices communicating over serial with a text based format with line endings are by no means expected and should not be relied upon to provide an entire packet at speed.

It just is more robust to do without things like "wait until", and then there is the matter of everything else stalling, which would make trouble for lotsa things ppl get up to.

a7

i don't think the extra effort to avoid blocking for some unusual unknown situation is worth the discussoin at this point.

in other words, unliess your appication has some real-time requirement that can't tolerate blocking and needs to deal with a transmitter that doesn't output a complete string with a terminatior ... use readBytesUntil()

You do you, NP.

a7

you're telling the OP that there are limited times when readBytesUntil() iis appropriate to use when the OP is strugling to understand char arrays.

this isn't a thread about blocking or real-time applications

So, the no blocks ever is key in day gig. Runs on ESP32 with some VERY simple user interface, really just a log but made easier to read and some simple parameter maint code. One piece of it - that I don't do or understand - is getting too big (76%) so decided that doing away with String objects may be a good idea. I like the Arduino IDE but of course know nothing of it's innards so decided to get re-familiar w/ string handling, which knowledge is at least 20 years old in my much older brain... Thought I'd just whip up a little string handling code in my spare time. Hence this thread. Couldn't even get the entry of a simple char array handled. Hate that. But now at least I can start fooling around with the str instruction set. I have this comment about waiting for the "complete" serial entry to arrive:

Since have no concern about blocking, maybe could just insert a delay(whatever) when .available 1st goes high. I did think it wouldn't go high until the eol was received because the typed chars don't appear on the serial screen until then

With the code you ended up with, there is no reason to add delay(). Just let loop(), um, loop until the terminating character arrives.

With readIntil(), you can set the timeout serial uses to the same whatever, or make it ridiculously big enough, since you are willing to wait.

a7

Yep. I am going to fix it so that loop() is the main loop, not one I build inside it. Haven't studied the readuntil(), that also in my evening plans. Thanks

seems that the problem is more about the underlying serial interface to the UART on the Arduino and how char data is typical transmitted.

the code just reads (unqueues, but doen't print) chars that are available,. but there's a 5 msec delay between checks (i.e. if (Serial.available()). there's ~1 char/msec at 9600 bps.

so between checks, some # of bytes is received, they are quickly unqueued, and then the code waits 5 msec and checks again, at which time ~5 chars could have been received

Input

it's easy to tell the depth of a well

Output

3
5
5
5
5
5
5
5
void
loop (void)
{
    int n = Serial.available ();

    if (0 < n) {
        Serial.println (n);
        do {
            Serial.read ();

        } while (Serial.available ());
    }

    delay (5);
}

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

size_t Stream::readBytes(char *buffer, size_t length)
{
  size_t count = 0;
  while (count < length) {
    int c = timedRead();
    if (c < 0)
        break;
    *buffer++ = (char)c;
    count++;
  }
  return count;
}