Go Down

Topic: Why do some methods in my arduino library not see class variables (Read 2669 times) previous topic - next topic

nubble

Hi,
I have been developing a arduino library called Mote which is performing a function similar to a Remote Terminal Unit used in SCADA.
I have many methods calling other methods within the class, and some variables are class variables so I dont have to pass variables as arguments to internal functions.

My problem is that certain methods have gibberish in the global class variables while others are fine. The way I tested this is with print statement for debugging.

The global class variables are defined as public in the Mote.h as:
Code: [Select]

char* topic;
char* payload;
int payloadLength;


Then the sketch hands data to the library through the method call:
Code: [Select]
mote.execute(topic,payload,payloadLength);

The method for execute is:
Code: [Select]

void Mote::execute(char* topic, char* payload, int payloadLength){
this->topic = topic;
this->payload = payload;
this->payloadLength = payloadLength;
Serial.print("Mote::execute: topic = ");
Serial.println(this->topic);
Serial.print("Mote::execute: payload = ");
Serial.println(this->payload);
bool validMsg = AL_decode();
//Serial.print("Mote::execute: validMsg: ");
Serial.println(validMsg);
if(validMsg){
if(String(protocol.objType)=="$L"){
Controller_latch(conversions.charA2int(protocol.index), String(protocol.value));
}else if(String(protocol.objType)=="$P"){
Controller_pulse(conversions.charA2int(protocol.index), String(protocol.value));
}
}
//reset the protocol variables
resetProtocol();
//reset the global topic and payload variables
resetTopicAndPayload();
}


Here the variables to the execute method are stored into global class variables. I did some debug prints here to check:
Quote
Mote::execute: topic = 1/11/$L/101
Mote::execute: payload = 1
Mote::AL_decode: topic =
Mote::AL_decode: payload = 1
Mote::decode: srcNode= 0
Mote::decode: destNode: 0
Mote::decode: objType: 
Mote::decode: index: 
Mote::decode: value: 1
0


Then execute calls AL_decode() which has the first few lines of code below:
Code: [Select]
bool Mote::AL_decode(){
//Decode topic and payload into system variable protocol struct c/o src,dest,objType, index, value - old rxFromNetwork
///topic = "1/11/$P/151";
//payload = "1000";

Serial.print("Mote::AL_decode: topic = ");
Serial.println(this->topic);
Serial.print("Mote::AL_decode: payload = ");
Serial.println(this->payload);


So somehow based on the print debugs the AL_decode() get garbage data as the global class variables, specifically for "topic".

Why is this so? Am I out of RAM?

I am running this on an Arduino Uno with the sketch size of 9324 bytes.

Please any help would be appreciated.
Thanks in advance
Jason


AWOL

Post your code.
Just because you're only using 9k of program memory doesn't mean you can't be running out of RAM

majenko

What is the "topic" that you're passing to execute?  You do know you're only passing a pointer to the data, not the data itself, don't you, and that it is your responsibility to ensure that the memory pointed to by that pointer is really valid all the time?

I.e., NEVER pass a pointer to a LOCAL variable to a class that uses the value GLOBALLY.

nubble

Hi
Please find code attached. 3 more files to follow in next post

nubble

Here is the rest.

PaulS

First file I opened:
Code: [Select]
char cha[str.length()+1];
        str.toCharArray(cha,str.length()+1);
//Serial.print("Conversions::string2CharA: cha = ");
//Serial.println(cha);
return cha;

Nonsense. You can't return a pointer to a local variable. The variable goes out of scope when the function ends, and the memory can be reused at any time.

First thing you need to do is ditch the String class completely.
The art of getting good answers lies in asking good questions.

nubble

Majenko: the topic is "1/11/$L/101". I get what youre saying but dont kow how to implement this. Can you give me some pseudocode pls?
Thx

majenko


Majenko: the topic is "1/11/$L/101". I get what youre saying but dont kow how to implement this. Can you give me some pseudocode pls?
Thx


No, the content of the memory pointed to by "topic" at the time execute is called is "1/11/$L/101".  "topic" is a pointer.  What does it point to, exactly?

nubble

From the responses I got from you I then went to the source of "topic" where the original pointer was created. this is in  MoteTransport in a method called convertToTopicAndPayload(). I then immediately instead of creating a local pointer in the method, rather used a class viariable pointer called this->topic.

The problem is that I need to completely decouple code in the MoteTransport from the Mote class and am trying to define a standard interface  through callbacks  between the two.
Code: [Select]
typedef void (*moteTransportCallbackT) (char* topic, char* payload, int payload_length);
Because other transport protocol layers that adhere to the same interface can then be used. for example the MQTT transport layer adheres to this interface and can be used instead of MoteTransport. Now I have no control over their code and their pointers.

So what actually happens is that the Transport libs [MoteTransport and MQTT] are polled from the sketch and detect incoming RFM12B radio traffic. On data receipt they call the callback in the sketch as defined above. The sketches implemented callback then hands the arguments to the Mote.execute which has a similar method definition to the transport interface:
Code: [Select]
void execute(char* topic, char* payload, int payloadLength);

So my question is how do I handle pointers and their scope in other peoples libraries without modifying their code?
would memcpy or strncpy work at the sketch level?

thanks in advance

majenko

Here's an example of what is happening using simpler variables:

Code: [Select]

char *global;

void setup() {
 char text[10];
 sprintf(text, "12345");
 global = text;
 Serial.begin(9600);
}

void loop() {
 char local[10];
 sprintf(local, "abcdefg");
 Serial.print("Global = ");
 Serial.println(global);
 Serial.print("Local = ");
 Serial.println(local);
 delay(1000);
}


That results in:
Code: [Select]

Global = cdefg
Local = abcdefg
Global = cdefg
Local = abcdefg
Global = cdefg
Local = abcdefg
...

which is not what you were expecting.

So what exactly is going on here?

Ok.  So we have a "global" pointer where we think we're going to store some data.  Then in setup() we create a variable, into which we put "12345".  We then assign that variable to the "global" pointer.

Then in loop() we make another variable, put "abcdefg" into it, and print it along with the "global" pointer.

But surely that should work, right?

Wrong.

It's all down to how the variables are created, as well as what the variables are.

Local variables (which are declared inside a function), like "text" or "local" in the code above only actually exist while you're inside that function, or within functions called by that function (and so on down the chain).  As soon as you leave the function the variables are gone.  The space for local variables is declared "on the stack".  The stack is a flexible storage area that grows and shrinks.  You can think of it as a stack of coins where each coin is a variable.  When you enter the function coins are put on the top of the stack of coins, one for each variable, and when you leave the function the coins are removed again.  There should always be the same number of coins removed as were put on, otherwise the stack will grow and grow until the system crashes.

So the "text" variable, which is assigned to "global" is being lost as soon as setup() exits.  But you assigned it to global, though, so it's stored there isn't it?

No.

Now we look at what the variables are.

Doing that kind of assignment is perfectly fine when you're dealing with simple numbers, where the number itself is stored in the variable - you assign the variable and it copies the content.  That's not what a character array (read: string) is.  Remember, the Arduino is an 8-bit CPU.  That means that only 8 bits of data can be stored in any one location.  "byte" and "char" types are stored as such.  "int" variables are made up of two memory locations as they are 16-bit values, but the compiler understands those and deals with them as a single number.  But what is a string of characters?  That's a chunk of 8-bit values.  You can't store that lot in a single 8-bit value, nor in a 16-bit "int" value.  So instead the variable just contains the "where", not the "what" of the variable.  Instead of the variable containing "12345", it contains, say, the number 1736, which is the address of a memory location.  That memory location contains the value "1", and the string continues from there, with 1737 having "2", 1738 having "3", etc.

So when you assign a pointer variable to another you're not copying the content of the string, only the address at which that string is located in memory.

And... that string is located in the stack, so the memory will be "lost" when the function returns, and re-used by the next function to be called - hence it is being overwritten by "cdefg" as the "local" variable now occupies the same memory area.

So... what can you do to work around it?

Well, there's three possibilities:

1. Use global variables instead of local variables

Global variables are always going to be there wherever you are in the program.  Their addresses won't change, and they won't get overwritten by other data.  They can make your program a little more messy though and harder to understand.

2. Copy the data

Only applicable if you don't mind modifying the library (or other destination of the data).  Instead of just storing the pointer in the library you have a separate character array waiting to receive the data.  Then instead of a simple assignment (a = b) you copy the content of the data (strcpy(a, b)).  The destination character array has to be big enough to accept the data you are sending, and this can be wasteful of memory if you're allocating more than you really need most of the time.

3. Make your local variables "static".

Code: [Select]
static char text[10];
Static variables are stored like global variables (in the same block of memory), and are treated the same way as far as their memory is concerned, however their "scope" (where the name of the variable is "known" about) remains local.  The memory for the string is permanently allocated and won't be released when you exit the function, protecting your data.  It does waste memory though as even when you're not using the variable it's memory is "locked".  Also, static variables retain their values across calls to the same function, which can both be good and bad if you're not expecting it.

Now, what if the values you are working with don't change?  They're just static strings that you send to the library at different times?  Well, those shouldn't even be in RAM at all.  You should be storing them in flash.  That's a whole other can of worms though that I won't even go into here - just go look for the many tutorials on flash strings on the web.

nubble

Majenko: Thank you for taking the time and for this detailed explanation of the arduino variable environment. I really appreciate this and with this explanation will write better code from here on. It is a very different to the python environment I am used to.

My approach for my lib from here on is to move all char* to class variables with strncpy as soon as I have access to them from my libs. My lib classes and their global variables should stay in scope within the lifetime of the sketch [I hope this is true] and in this way I can ensure my char* pointers never losing their scope and addresses.

thanks again.

Go Up