Append C-string (Char Array) to String

Hello Humans,

I am having trouble wrapping my head around C-Strings VS Strings and the proccess behind moving data between each other.

As far as i have worked out: (I am probably wrong)
A C-String/Char Array is

  • Defined as char receivedChars[numChars];
  • When viewed contains an array like ["M","1","0","0","P","1","0","0","S","0","4","0"]
  • Each index is considered a Char even when containing more than one character and can be editied using Char Operators.
  • Can only have data added if space has been previously allocated for it (Still unsure how to allocate array size outside of definition.)

A String is

  • Defined as String IAMASTRING;
  • When viewed contains M100P100S040
  • Is not the sum of its parts, is only a string. Cannot be referenced using Char Operators.
  • Can dynamically allocate its own memory as it is written to.

(I have no idea if C-string and Char Array are different names for the same thing. Stack exchange forums seem to refer to them as the same. )

I've tried using strcat and a few other functions to append a string onto a C-string/Char Array with no luck.
I manage to get a clean compile using a for loop but i havent tested this working yet.

for (int a = i; i < (CMDChars + i); i++){
     CMD += receivedChars[a]; /// Add Elements to CMD String
}
  1. My main code takes in serial data;
  2. Saves it to a C-String/Array that is pre-allocated 32(bytes/cookies)? ; "writing over itself from 0 index if exceeding 32"
  3. Searches through each index for an alphabetical character.;
  4. If it finds an alphabetical character it searches the next 3 indexs for a number 0 to 9;
  5. If 3 fails, use a for loop to move all C-string elements - 1 starting from 0 index;
  6. if 4 fails, use a for loop to move all C-string elements - 4 starting from 0 index;

Then a later date I would like to set the allocated char array to reduce its size after receiving data. Then increase its size to 32 again at the start of data receiving.

TLDR:
If someone can help me clarify the differences and pros and cons between moving data between strings/CharArrays/C-Strings that would be sweet.

I kind of made a mess of this post but i didn't want to leave any information out so here is the code ive got so far.
Func at line 152 is where it starts. A note to make is that i am using FastLED which fudges serial data. So im writing into a C-string/Array to get serial reading done ASAP.

///#define FASTLED_ALLOW_INTERRUPTS 0
#include "FastLED.h"
#include <SoftwareSerial.h>

#define NUM_LEDS 144 ///144
#define PIN       3     // LED signal Pin

SoftwareSerial HM10(9, 8);///9 (RX) ///8 (TX)
CRGB pixels[NUM_LEDS];

int     leds[294]; ///For Aligning Vertical/Horizontal Motion /// NUMBER OF LEDS IN ONE CIRCLE~?leds[NUM_LEDS/4];
int     max_overall_brightness = 255;
int     overall_brightness = 255;
float   red_max = 1;
float   green_max = 1;
float   blue_max = 1;

int red;
int green;
int blue;

int     animation_rate = 5;
int     MAXanimation_rate = 10;
int     mode = 1; // Animation mode index
int     MAXmode = 14; // max mode index

int incoming_command = 0;
int animation_speed = 2;

// These variables are used within the animation methods.
// They are scoped by name, but global in the sketch,
// so that animations will not restart when commands are received.

///////////////////////////////Serial BT Receiver
int Power;
const byte CMDChars = 4;
const byte numChars = 32; //array size limiter
char receivedChars[numChars]; // Array of received data
boolean RecNewData = false;
boolean SendNewData = false;
static byte ndxRx = 0; // index size of "received data" storage
static byte ndxSort = 0; // index size of array
bool NewDataRx = false;
bool RxValidData = false;
String CMD;
char CSV[2] = {',','\0'};
//char AAA[4] = {'M', '1', '3','\0'};
//char BBB[3] = {'S', '4','\0'};
//char CCC[4] = {'V', '8','0','\0'};
//char DDD[3] = {'P', '1','\0'};
///////////////////////////////
int DataCleanUp = 0;
////////////////////    
//BeaconEDIT Vars
int VerticalLEDs = 6; 
int HorizontalLEDs = 24; //LEDS in one circle

// Beacon Vars
double beacon_angle = 0;
int    beacon_led_angle = 0;
double beacon_left_distance = 0;
double beacon_right_distance = 0;
double beacon_absolute_distance = 0.0;
int    beacon_beam_width = 10; // in degrees
int    beacon_beam_width_Half = (HorizontalLEDs / VerticalLEDs);
int    beacon_falloff = 35; // in degrees
int    beacon_pixel_brightness = 0; // in degrees
bool CanReadSoftSerial = true;

volatile boolean animate = true;
volatile long animation_change_timeout;



void setup() {
  LEDS.setBrightness(255);
  FastLED.addLeds<WS2812B, PIN, GRB>(pixels, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setMaxPowerInVoltsAndMilliamps(5, 700);    // limit power of LED strip to 5V, 500mA
  FastLED.clear();

  Serial.begin(38400);
  HM10.begin(38400);
  Serial.println("Setup Complete");

}



void loop() {
//  unsigned long start = micros();
  recvWithStartEndMarkers(); // Receive BT serial data
//  unsigned long end = micros();
//  unsigned long delta = end - start;
//  Serial.print("SerialBufferScan = ");
//  Serial.println(delta);
  DataChecker();
  SortRxData ();
}

void recvWithStartEndMarkers() {
    NewDataRx = false;
    RxValidData = false;
    char IncomingChar;
//if (HM10.available() == 0){  //////Clock speed determins animation speed
//      FastLED.show();
//    }
      
    while (HM10.available() > 0 && RecNewData == false) {
        IncomingChar = HM10.read();
        Serial.print("IncomingChar:");Serial.println(IncomingChar);
        HM10.print(IncomingChar); // Send CheckByte
        
        HM10.flush(); ///WAIT FOR DATA TO SEND
        
        delay(2);  ///WAIT FOR LAST DIGIT OF DATA TO SEND.
        
        if (ndxRx >= numChars) { // if index exceeds byte limit
//           ndxRx = numChars - 1; // set index to re-write over last char in array
           ndxRx = 0; // set index to re-write over last char in array
        }
        receivedChars[ndxRx] = IncomingChar; // index chars in array index ndx
        ndxRx++; // increase index number
//        Serial.print("ndxRx:");Serial.println(ndxRx); 
//        Serial.print("RxCharArray:");Serial.println(receivedChars); 
//        Serial.print("RxCharArraySize:");Serial.println(strlen(receivedChars));

          RxValidData = true;
          
        }

        
    }
//    if (HM10.available() == 0){  //////Clock speed determins animation speed
//      FastLED.show();
//    }

//}


//Just for completeness:
//
//read first c-string array index:
//if c is (Any Letter): get next character
//if c is digit: get next character
//if c is something else: stop and remove from c to last digit from c-string.
//if there are no more characters: Change checker bool to false; 

//isalpha() = letter OR? isupper()?
//isdigit() = number

void DataChecker() {
  int i = 0;
  int x = 0;
  
  if (RxValidData == true){
    for (i = 0; i < receivedChars; i++){ /// For every element in recChars AKA "C-String"
      if (isalpha(receivedChars[i])) { //  if element is alphabetic letter
        for (x = i; i < (CMDChars + i); i++){ /// for 3 indexs above element i
          if (isdigit(receivedChars[x])){         /// if element is number
            DataCleanUp = 0; //SwitchCase to save
          }
          else {
          DataCleanUp = 2;  // Remove from X to I index from Cstring
          break; // Exit For-Loop with Last Numbers Index as x
          }
          }
       }
       else {
        DataCleanUp = 1; // Remove First Index from Cstring
        break; // Exit For-Loop with First Letters Index as i
       }
      }
    
    switch(DataCleanUp){
    
      case 0: //Valid Receive create CMD string
      for (int a = i; i < (CMDChars + i); i++){
        CMD += receivedChars[a]; /// Add Elements to CMD String (DONT FORGET TO EMPTY STRING AFTER USAGE)
      }
      break;
      case 1: // INVALID RECEIVED remove first index from C-string /// CSTRING = Get length of string; MOVE i+1 to i; MOVE i+2 to i+1; MOVE i+3 to i+2; MOVE i+4 to i+3; MOVE i+5 to i+4
      for (int p = 0; p <= (receivedChars.length()); p++){
        receivedChars[p] = receivedChars[p+1];
      }
        
      break;
      case 2: // INVALID RECEIVED remove X to I index from C-string /// CSTRING = Get length of string; MOVE x+1 to i; MOVE X+2 to i+1; MOVE X+3 to i+2; MOVE X+4 to i+3; MOVE X+5 to i+4
      for (int d = 0; d <= ((receivedChars.length()) - x); d++){
        receivedChars[d] = receivedChars[((x+1)+d)];
      }
      break;
      default:
    
      break;
    }
  }
}

void SortRxData (){
  if (RxValidData == true){
    CMD = String(receivedChars);
    
    Serial.print("CMD:");Serial.println(CMD);
//    Serial.print("RxCharArray:");Serial.println(receivedChars); 

    for (int i=0; i <= (numChars-1); i++){
      receivedChars[i] = NULL; // Set all array to null      
    }
//    delete[] receivedChars; // Delete array to free memory
//    Serial.print("RxCharArrayCleared:");Serial.println(receivedChars); 
    CheckNewDataFunc();
  }  
}  



void CheckNewDataFunc() { /// Find values in received Data
   
  if (CMD.indexOf('M') == 0){ // if mode('M') char found
    if (CMD.indexOf('1') == 1){
//      Serial.println("ITS An M+");
        if ((mode < MAXmode)&&(mode != 99)){
          mode += 1;
        }
        else if (mode == 99){
          mode = 0;
        }            
    }
    else if (CMD.indexOf('0') == 1){
//      Serial.println("ITS An M-");
        if ((mode > 0)&&(mode != 99)){
          mode -= 1;  
        }
        else if (mode == 99){
          mode = 0;
        }
      }
    Serial.print("mode:"); // print mode result
    Serial.println(mode); // print mode result               
    }
    
  else if (CMD.indexOf('S') == 0){ // if mode('M') char found
    if (CMD.indexOf('1') == 1){
      Serial.println("ITS An S+");
//      if (animation_rate < MAXmode){
//        animation_rate += 1;
//      }            
    }
    else if (CMD.indexOf('0') == 1){
      Serial.println("ITS A S-");
//      if (MAXanimation_rate > 0){
//        animation_rate -= 1;  
//      }            
    }
  }
          
  else if (CMD.indexOf('V') == 0){ // if mode('M') char found
    if (CMD.indexOf('1') == 1){
//      Serial.println("ITS A V+");
      if (overall_brightness < max_overall_brightness){
        overall_brightness += 5;
      }
    }
    else if (CMD.indexOf('0') == 1){
//      Serial.println("ITS A V-");
      if (overall_brightness > 5){
        overall_brightness -= 5;  
      }
    }
  }
                  
  else if (CMD.indexOf('P') == 0){ // if mode('M') char found
    if (CMD.indexOf('1') == 1){
//      Serial.println("ITS A P+");
      mode = 0;  
      Power = 1;            
    }
    else if (CMD.indexOf('0') == 1){
//      Serial.println("ITS A P-");
      mode = 99; 
      Power = 0;          
    }
  }
  else if (CMD.indexOf('R') == 0){ // if Red ('R') char found
    String CMDVal = CMD;
    CMDVal.remove(0,1);
    int Rxred_max = CMDVal.toInt();
    green_max = ((float)Rxred_max / 255); /// convert 255 into decimal for animations        
    Serial.print("IT IS RED:");Serial.println(red_max);
//    incoming_command = 99;
  }
  else if (CMD.indexOf('G') == 0){ // if Red ('R') char found
    String CMDVal = CMD;
    CMDVal.remove(0,1);
    int Rxgreen_max = CMDVal.toInt();    
    red_max = ((float)Rxgreen_max / 255); /// convert 255 into decimal for animations    
    Serial.print("IT IS GREEN:");Serial.println(green_max);
//    incoming_command = 99;
  }
  else if (CMD.indexOf('B') == 0){ // if Red ('R') char found
    String CMDVal = CMD;
    CMDVal.remove(0,1);
    int Rxblue_max = CMDVal.toInt();
    blue_max = ((float)Rxblue_max / 255); /// convert 255 into decimal for animations
    Serial.print("IT IS BLUE:");Serial.println(blue_max);
//    incoming_command = 99;
  }
  else {
    for (int i=0; i<numChars; i++){
      CMD[i] = NULL;
    }
  }
Serial.println("///////////LOOP////////////");
} 





A cstring is a char array that ends with zero. A char array contains chars (‘x’) not cstrings (“x”), there cannot be more than one char in an element of a char array. If you have a string “abc” it’s the same as a char array [‘a’,’b’,’c’,0] so to concat a string and a char array your array needs to end with zero to be a cstring. Now String is a wrapper around char array that creates new array if old one is too small if you add characters to it for instance…

1 Like

Strings are a convenient wrapper around c-strings (character arrays).
The C languages have no awareness of Strings. C language operators can’t work with Strings.

Strings are fine with large memory models, and fast processors - simply because you’re throwing away a good number of cpu cycles to manipulate the c-strings underneath the bonnet.

Yes, they’re convenient and easy to play with, but massively inefficient - like interpreters vs compilers. There is a difference.

This is usefull. I made the assumption they where identical.

Is that only a zero? Only i have seen null, \0 thrown around alot when people are talking about char arrays. Are they all the same or does it have to be a 0. Alot of my data received ends with zeros...

So String is basically a function within itself?

And remember that those characters are just numbers. They're ascii codes. So what's really in that array is:
[77, 49, 48, 48, 80, 49, 48, 48, 83, 48, 52, 48, 0]
You can use any sort of math you want on them.

A String (capital S) is just a class wrapper around a char array. Inside is a char array. And it has a bunch of convenient functions that allow you to work with strings a little more easily. It comes at the cost of memory. When you add to a String it does just like you'd have to do with a char array, it goes and creates a larger char array and moves your entire string there. That's why you can get into so much trouble adding Strings together with += because it ends up using a lot of memory.

There is NOTHING that a String can do that a char array cannot. There is PLENTY that can be done with the char array style string that cannot be done with a String.

They are different names for the same thing. A char array doesn't necessarily have to hold a bunch of text. char is just a signed byte type. But it's the type we use with ascii codes. And storing text in a char array is a c-string.

That for loop makes no sense without some other code. But I think I see what you're after.

If you want to copy part of a c-string you can use memcpy or you can just loop through and take character by character.

No you don't. Just keep your 32 bytes allocated. You may feel like you're saving them for something else, but if you use them for something else then they're not available for this anymore. And keeping up with whether or not those 32 bytes are available would end up taking more code and memory than it would save.

You need a circular buffer. Then you just update your indices and forget moving all this stuff. Look at the implementation of the Serial class for a great example.

1 Like

Yes, NULL, ‘\0’ and 0 are the same

String is ‘class’ to be specific

'\0' == 0

They're the same thing. But when you put it in quotes it needs an escape because

'0' == 48

So would you suggest i take ndxRx from line 123
And use that to specifiy the length for DataChecker() line 152?

Thank you all for your replies i think i have a much better understanding of the differences now.
I am here reading these replies. Lots of information. Will get back with more once I can read them without interrupts.

The purpose of the zero in a cstring also called ASCIIZ strings is to mark the end of the string because you don’t know the length upfront unless you store it separately. Then to iterate over all elements of the string you go through the array from beginning to zero element.

Do you know if

 if (isalpha(receivedChars[i])) { 

would be checking ASCII or does isalpha() read as char?

cpprefernce says:

Notes

Like all other functions from , the behavior of std::isalpha is undefined if the argument's value is neither representable as unsigned char nor equal to EOF. To use these functions safely with plain chars (or signed chars), the argument should first be converted to unsigned char:

So i would have to convert the char array into an unsigned char to check if it contains alphabetical?

I understand that part. But when i receive data i am basically overwritting values inside the char array. Once i've finished writing should i assign +1 value to NULL? If i should. Then should i also only allow the loop to itterate over 31 indexs to always leave space for an escape character?

It’s a cString only if you add the null char at the end. Otherwise it’s malformed
Ie a cString is a char array holding a null byte somewhere (to denote the end of the text, does not have to be the last index in the array)

Yes if you want to use your array as a string after you created it you should leave space for zero at the end.

I am unsure how many of you read my rage post from a year ago but i honestly have had a great few months on here with some wonderfull responses. Thank you all again.

I assume by malformed you mean "Your doing it wrong"

TBH I would leave it in receivedChars and parse from there. Once you have a valid transmission, parse it, clear it, and then go back for another.

CheckNewDataFunc() could look something like:

for(int i=0; i<numChars; i++){
   if(receivedChars[i] == 'M'){
          i++;   //Move up to next character
         if(receivedChars[i] == '1'){
              // do whatever
         } else if (receivedChars[i] == '0'){
             // do something different
        }
  else if(receivedChars[i] == 'P'){
       

etc. etc.

You could even make that series of if statements for the different letters into a switch/case.

If you forget the null lots of weird stuff happens because nobody knows where your string ends. If you try to print it then it will just keep printing whatever is in memory behind your string on and on until it hits a zero.

char c[5] = "Hello";
Serial.print(c);

will print Hello followed by a bunch of random garbage.

c-strings are a really good way to learn about pointers.
Makes processing a character array so much more powerful.

char myText[32];
char *mtptr = myText;

and so on…

So what you're suggesting

itterate through the entire receivedChars array looking for valid transmittion,
Each time valid is found use it to change switch case.
else clear receivedChars array
end of loop

I've used pointers before. But it was a while ago. From what i remember they can point to a portion of an element e.g

char myText[5] = ['1','2','3','4','\0'];
char *mtptr = myText[3];
*mtptr != 4
*mtptr == the location where '4' is.

Did i get that right?