Traverse struct with pointer

Hi All,
If i have a struct like the following…

struct config_t
{
  byte mac[6];
  byte ip[4];
  byte subnet[4];
  byte gateway[4];
  byte server[4];
  char path[128];
};

can i set a pointer to the struct so that i can take some serial input and put each input byte into the struct all in one shot with out having to refer to each of the struct’s members individually.

I can’t seem to get this to work.
below is what i am currently doing, but it just seems kinda clunky.

void CheckSerial(){

  if (Serial.available() > 0){
    switch (Serial.read()){
    case 'W':
      {
        delay(1000);
        int i=0;
        while ((Serial.available()>0) && i<6) configuration.mac[i++] = Serial.read();
        Serial.println(i);
        i=0;
        while ((Serial.available()>0) && i<4) configuration.ip[i++] = Serial.read();
        Serial.println(i);
        i=0;
        while ((Serial.available()>0) && i<4) configuration.subnet[i++] = Serial.read();
        Serial.println(i);
        i=0;
        while ((Serial.available()>0) && i<4) configuration.gateway[i++] = Serial.read();
        Serial.println(i);
        i=0;
        while ((Serial.available()>0) && i<4) configuration.server[i++] = Serial.read();
        Serial.println(i);
        i=0;
        while(Serial.available() > 0) configuration.path[i++] = Serial.read();

        configuration.path[i++] = '\0';
        Serial.println(i);
        PrintConfig();
      }
      EEPROM_writeAnything(0, configuration);
      //PrintConfig();
      break;

    case 'R':
      EEPROM_readAnything(0, configuration);
      PrintConfig();
      break;

    default:
      Serial.flush();

    }

  }

}

Thanks in advance
Jeff

A union should work. A union allows you to refer to a section of memory in multiple ways. If you create a union between your struct and an array of bytes you can access the memory as bytes or your struct fields.

This was very handy for the real-time clock library I wrote. For transmitting bytes it was easier to use an array. For setting values using field names like sec, min. hour was easier.

There are probably simpler examples but you could look at my RTC library and example code at http://tinyurl.com/y8exe7k

(* jcl *)


www: http://www.wiblocks.com twitter: http://twitter.com/wiblocks blog: http://luciani.org

How about this - assign a pointer to the struct’s location in memory.

Send W, then the same amount of bytes to fill the struct.

void CheckSerial(){
  uint8_t *pPtr = (uint8_t *)&configuration;
  if (Serial.available() > 0){
    switch (Serial.read()){
    case 'W':
                // what if the W was erroneous... Watch for a timeout here perhaps?
                while(serial.available()<sizeof(config_t))
                    delay(100);
               
                  for(int i=0;i<sizeof(config_t);i++)
                       *pPtr++ = Serial.read();

        PrintConfig();

      EEPROM_writeAnything(0, configuration);
      break;
...

Spinlock - your solution will be problematical with the structure described above; the structure is 150 bytes long, and the Arduino's rx buffer is only 128 bytes.

Thanks for all the help this morning couple of questions for clarification purposes. is uint8_t the same as saying byte? why the cast to uint8_t when doing the address of assignment? being a noobie to C i would have written

byte *ptr = &configuration;

would this have worked? is the delay 100 to let the buffer fill up? i understand using sizeof in the for loop so you don't write past the end of the struct. but why use it in the while loop? the members in the struct are basically all byte size members. would size of work if some of them were ints or longs?

now that i can store and retrieve these things from the struct and the eeprom. i am trying to use them to call ethernet.begin and it is not working for some reason. it is very hard to debug as there are no functions to check with the ethernet class to see if things are getting assigned properly. any thoughts on this? I started a new topic in Arduino Forum ? Software ? Syntax & Programs ? "wiznet configuration settings in a struct" if you want to look at the code i'm using thanks for all your help spinlock Jeff

Thanks for that Groove, you’re right. (need more coffee.)

You can do it then without waiting for the whole data buffer to be in place…

void CheckSerial(){
  uint8_t *pPtr = (uint8_t *)&configuration;
  if (Serial.available() > 0){
    switch (Serial.read()){
    case 'W':
                // what if the W was erroneous... Watch for a timeout here perhaps?
                  for(int i=0;i<sizeof(config_t);i++)
                                                {  while(Serial.available()<1)
                                                      delay(100);
                        *pPtr++ = Serial.read();
                                                 }

        PrintConfig();

      EEPROM_writeAnything(0, configuration);
      break;
...

is uint8_t the same as saying byte?

Yes.

why the cast to uint8_t when doing the address of assignment? being a noobie to C i would have written Code:byte *ptr = &configuration;

The type of the expression "&configuration;" is "struct config_t*", so you can't (shouldn't) assign it to a variable of type "byte*"

[edit]That "delay(100)" is a possibly too long, if you're using serial speeds above 9600.[/edit]

The type of the expression "&configuration;" is "struct config_t*", so you can't (shouldn't) assign it to a varaible of type "byte*"

Excellent thanks!

what if the first element of the struct was an int or long? Would you still do the same cast?

what if the first element of the struct was an int or long? Would you still do the same cast?

You're not casting for the contents of the structure, only for the structure itself.

I hope this helps;

The configuration structure is stored in memory, for arguments sake let say it starts at address 100 and goes for 150 bytes, the last being memory address 249.

using: uint8_t (or byte) we assign another variable (I used the variable pPtr) to the starting address:

byte *pPtrByte = (byte*)&configuration;

pPtrByte now points to memory address 100.

If we used an int:

int *pPtrInt = (int*)&configuration;

It would also point to memory location 100.

However, because of how pointer incrementing works, when we increment pPtrByte like this: pPtrByte++; it now points to 101.

but... pPtrInt++; now increments the size of an int, and points to 104 (if an int is 32bit).

The next increment of the pointers would be 102(byte) and 108(int,32bit). and on and on.

You’re not casting for the contents of the structure, only for the structure itself.

I think i understand. so you change it to a byte pointer and then assign it to a byte pointer.

The next increment of the pointers would be 102(byte) and 104(int). and on and on.

got it. because an int uses two bytes.

You caught me in an edit, but yes.

If a pointer's type is 2 bytes, and you increment it, it increments 2 bytes.

If it is 4 bytes, it increments 4.

That's why a byte pointer is good - you can walk memory and hit every location.

Of course, with your configuration structure and the memory - we are assuming that it is packed one member immediately after another in memory. Some processors need to align members on word boundaries. sizeof(config_t) may be more than 150bytes on those architectures, and you would have to account for that.

You would want to think about if you were transferring your structure over serial in a binary fashion as you seem to be from other systems. ;)

great thanks!

any chance i could get you to comment on that other topic i mentioned that is related to all this?

topic is here Arduino Forum ? Software ? Syntax & Programs ? "wiznet configuration settings in a struct"

sorry here is a link

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1261109565

I wouldn’t use a union, nor would I cast the pointer structure to a byte structure. I’d do it this way:

#define MAC_SZ 6
#define IP_SZ 4
#define PATH_SZ 128

struct config_t
{
  byte mac[MAC_SZ];
  byte ip[IP_SZ];
  byte subnet[IP_SZ];
  byte gateway[IP_SZ];
  byte server[IP_SZ];
  char path[PATH_SZ];
}; 

struct config_t configuration;

void CheckSerial()
{
  int c;

  c = Serial.read();     // returns -1 if no chars available
  switch(c)
    {
     case 'W':
       read_configuration(&configuration);
       // store in EEPROM
       break;
     case 'R':
        // etc, etc
        break;
     default:        // this is here to show that you are handling the 
        break;      // default case and haven't forgotten about it
     }
}

void read_configuration(struct config_t *config)
{
    read_bytes(MAC_SZ,&(config->mac));
    read_bytes(IP_SZ,&(config->ip));
    read_bytes(IP_SZ,&(config->subnet));
    read_bytes(IP_SZ,&(config->gateway));
    read_bytes(IP_SZ,&(config->server));
    // have to do something else for 'path'
}

void read_bytes(int num_bytes, byte *b)
{
    int i = 0;

    while (i < num_bytes)
      {
      while (Serial.available() == 0)     // wait for byte
         ;
      b[i++] = Serial.read();
      }
}

When you’re reading from Serial, you have to remember that it has a buffer that is being filled with bytes as they are being received. This receiving goes on in the background, through an interrupt routine. Your program may be checking for characters faster than the buffer is being filled, and a packet of characters may be sent in multiple pieces. You can’t assume that because the first character of a packet is in the buffer (Serial.available() > 0) that all of the packet is there. That’s why you have to wait for each character.

For the same reason, you don’t want to do that Serial.flush() at the end of your case statement. Let’s say there is only one character available, and it’s a ‘Z’. Between reading that character and your flush, the interrupt routine puts a ‘W’ in the buffer. Your flush will then throw it away, which means your sketch will miss the ‘W’ command. Worst of all, it may work sometimes, and it may fail randomly.

Finally, you’ll need a different routine to read the ‘path’. The path will have to always be the same size, or there will have to be a terminating character, like a carriage return at the end, so your program knows when to stop reading. As someone else pointed out, the buffer is only 128 characters, so the whole config with a long path might not fit into the buffer. You need to loop, checking for an available character, putting it into ‘path’, and stopping when you reach 128 bytes or the terminating character.

Hope this info helps.

Regards,

-Mike

thanks mike, very helpful

how does this look for a function to read the path. be gentle :wink:

void read_path(char *pathPtr)
{
  int i = 0;
  byte c = Serial.read();
  while (Serial.available() && c != '\r')
  {
    pathPtr[i++] = c;
    c = Serial.read();
  }
}

jeff,

How about this:

void read_path(int max_path,char *path)
{
    int c;
    int i = 0;
    byte done = false;

    while (!done)
      {
      if (i >= max_path)
        done = true;
      else
         {
         while (Serial.available() == 0)     // wait for byte
           ;
         c = Serial.read();
         if (c == '\r')
           done = true;
         else
           path[i++] = c;
         }
      path[i] = '\0';
      }
}

Please note that path must be big enough to hold max_path + 1 bytes, because of the NUL terminator. Also, there’s an error in my previous code. You don’t need the ‘&’ for the structure elements. Here’s the corrected code:

struct config_t
{
  byte mac[MAC_SZ];
  byte ip[IP_SZ];
  byte subnet[IP_SZ];
  byte gateway[IP_SZ];
  byte server[IP_SZ];
  char path[PATH_SZ + 1];
}

void read_configuration(struct config_t *config)
{
    read_bytes(MAC_SZ,config->mac);
    read_bytes(IP_SZ,config->ip);
    read_bytes(IP_SZ,config->subnet);
    read_bytes(IP_SZ,config->gateway);
    read_bytes(IP_SZ,config->server);
    read_path(PATH_SZ,config->path);
}

Hi Mike, Thanks , again very helpful to see how others work out these problems.

Correct me if i am wrong, but it looks like to me that your read_path function will put the terminating nul character in on every iteration of the while loop? Should it maybe be moved down one curly brace? Jeff

Jeff,

Good eye. It will work where it is, but it only needs to be done once, so after the while loop is a better place for it.

Thanks,

-Mike