How Can Hairy Global Variable be Hidden Away?
Started by 4 years ago●12 replies●latest reply 4 years ago●177 viewsHi there, sorry to bug you with yet another stupid coding question (I R a hardware design engineer). This is related to my 12x12 ping pong ball array where each ball contains a tricolor LED (https://www.clivemaxfield.com/awesome-12-x-12-ping...)
Someone suggested I use the array to scroll text messages, so I thought I'd give it a whirl. I'm using the Arduino IDE. As part of this I have a global variable in the form of a three-dimensional array of 0s and 1s defining the 7x5 dot patterns used to build the characters:
uint8_t CharTable[NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS] =
{
// Lots of 0s and 1s
};
The problem is that the contents of this array amount to about 550 lines of code. My first thought was to simply stick this declaration at the end of the program, but then I get a warning message saying that it's not recognized when I try to access it in a function.
There's no problem if I move the declaration to the top of the program above the function calls. I'm a bit puzzled here, because -- although global variables are usually declared before main() by convention -- I thought the C/C++ multi-pass compiler allowed they to be declared anywhere in the code (outside of a function).
Next I tried leaving the declaration (as shown above) at the top of the program, but replacing the body (where it says "// Lots of 0s and 1s" above) with a #include as shown below:
uint8_t CharTable[NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS] =
{
#include "AcsiiTable.txt"
};
I put the text file in the same folder at the sketch, but the compiler says "file not found"
So, do you have any suggestions as to the best way to handle this?
Thanks as always -- Max (1/2 man, 1/2 beast, 1/2 wit)
The compiler needs a little help and that is done by declaring a forward declaration. In C this is done with the extern key word like this:
extern uint8_t CharTable[NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS];
be sure NUM_ASCII_CHARS,NUM_CHAR_ROWS, NUM_CHAR_COLS are declared before the above declaration.
Place the above statement in the top of the program then you can declare the array at the bottom. However I'd recommend placing it into a separate .c file. if Arduino IDE can support multiple files. In this case I would create two files say array.c and array.h. Place the extern declaration in array.h and the actual array in array.c then include the array.h in your program, making sure the Arduino IDE is comipling the array.c file as well as you program file.
AWESOME!!! I just tried using the "extern" approach and it worked like a charm/ Every day I learn something new -- thank you for your sage advice!!!
Two comments, apart from the 'extern' business that "hodgec" has already told you.
1. You are wasting space. You store 1 bit in one byte. You could store all the bits of your 7 rows in one byte, and use bit-wise operators to get the bits. That would save you a lot of space; a character using only 5 bytes instead of 35.
2. You did not use the 'const' keyword in the declaration. That forces the compiler to place your stuff in the RAM, because it should be modifiable data. Thus, you lose both FLASH space (where the initialisation data is stored) and RAM (to where the initialisation data will be copied at boot time).
It probably doesn't matter in your current project, but if you are going to work with bare-metal embedded systems that are cost sensitive, then you will use resource constrained MCU-s where those things will be an issue. Of course, there will be other considerations (e.g. speed, as RAM is usually faster to access than FLASH) but it is useful to know where your data is stored and how much space it needs.
I've been thinking (don't laugh; it's true). Would you agree that:
-- For software developers creating applications to run on high-level systems like PCs, the keyword const applied to a variable just means that variable cannot be changed under software control.
-- For developers of embedded systems, it has that additional connotation that the variable will be stored in the flash memory and not in the SRAM.
What say you?
Well, the const keyword is a bit of a misleading thing. If you read the C standard it turns out that the const keyword means the following, not more, not less:
"I promise to the compiler that I will not change this variable in the scope where the const keyword applies."
That does not mean that the variable is a constant; in fact in embedded systems you will find things like this:
extern volatile const uint32_t something;
where the simultaneous use of both the volatile and the const keywords would suggest that the programmer was insane while in reality 'something' is a read-only hardware register that the program should not (and possibly cannot) change, but the underlying HW can, and does.
The compiler has a right to ignore the const keyword. It can take the keyword into account and give you a warning if you change a const object, but it can't actually stop you doing that:
const int x = 1;
*(int *) &x = 3;
will compile without a warning as you used an explicit cast telling the compiler that you know what you're doing. Of course, if the compiler placed 'x' in FLASH, then your program will fail at run-time but that's an other issue.
But since you promised the compiler that you won't change x it has a right to put x into the FLASH. Or, on a desktop system, it can put it into the code segment and set the write protect attribute on the segment, in which case your code will crash if you try to go around the compiler's back and sneakily write to x.
Gcc normally puts your const declared initialised data into a segment called .cdata. It is actually up to your linker script to put it into the FLASH. It may not be that simple, though, because for some Harvard architectures (for example the AVR cores) the FLASH and the RAM are not mapped into a unified address space and you can't just access the FLASH the same way as you do the RAM. So it is not a clear-cut issue, you need to know what you are doing.
However, if you will not write something, then it is always worth it to use the const keyword as it might help the compiler to optimise code and/or save space.
2. This is a really good point -- as you say, probably not significant in the case of my current project, but I really should get in the habit of doing things the right way. Also, to be honest, I hadn't realized that using the keyword const would simply lock things down in the flash (of course, it makes total sense now you mention it).
One question -- do I have to use the const keyword with the extern statement at the front:
const extern uint8_t CharTable[NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS];
Or with the full-up declaration at the end of the program -- or with both?
1.
I agree that I'm wasting space, but I'm using this as an example for
beginners who have less of a clue at at C than me (believe it or not).
Below are the lines in my 3D array associated with the ASCII character
for the number 0:
{
{0,1,1,1,0}, // 48, 0x30, '0'
{1,0,0,0,1},
{1,0,0,1,1},
{1,0,1,0,1},
{1,1,0,0,1},
{1,0,0,0,1},
{0,1,1,1,0}
},
The other way around, extern const. The order is important; if you use const extern the compiler will complain. The keywords extern, register, static and auto are storage class specifiers and they must come first. The const, volatile and restrict (and, since C11, _Atomic) keywords are type qualifiers and where they are in the declaration can affect its meaning:
char * x; // The mutable X points to a mutable char
const char * x; // The mutable X points to a constant char
char * const x; // X is constant, pointing to a mutable char
const char * const x; // X is a constant, pointing to a constant char
As per binary numbers, if you use a gcc based development system, then there's a gcc extension that lets you to specify binary constants:
a = 0x85;
a = 0b10000101;
and gcc extensions are enabled by default, they only get disabled if you specifically ask for a particular C standard to be followed and even then some might remain enabled unless you also tell the compiler to be pedantic about the standard.
Thanks so much for taking the time to explain this stuff. Just one more question (for now LOL)
Do I have to use both extern and const at the front of the program before I use the array:
// beginning of program
extern const uint8_t CharTable[NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS];
// functions and stuff go here
And then use them both at the end when I officially declare and populate the array:
// end of program
extern const uint8_t CharTable[NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS] =
{
//
};
Or can I miss one or both of them from the end (if I can, should I?)
If you use Arduino, I believe you must be using C++, not C (there are... differences), that supports the 0b binary notation as standard.
However, I would not be too worried to expose your audience to "binary to hex" "encoding". I remember as a young kid, reading how to create sprites for the VIC-II chip and I spent the summer drawing sprites on graph paper and then encoding them by hand to decimal during those boring days at the beach. There is a little bit of learning curve but I believe the reward is well worth it.
However keep in mind what user Kocsonya said, often it's "not enough" to simply declare a const and be sure that the resulting artifact will conserve a copy in the flash only. Often in Arduino one has to use the F() macro, but not in all cases.
Hi Manoweb -- thanks for your feedback -- On the one hand I'm tempted to go back and change everything to binary strings -- on the other hand, I have memory to spare and I've already created the array as 8-bit integers (there's an afternoon I won't see again LOL)
I haven't heard back from Kocsonya yet regarding the following question (perhaps I've worn him/her out) -- can you cast any light on this:
Do I have to use both extern and const at the front of the program before I use the array:
// beginning of program
extern const uint8_t CharTable[NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS];
// functions and stuff go here
And then use them both at the end when I officially declare and populate the array:
// end of program
extern const uint8_t CharTable[NUM_ASCII_CHARS] [NUM_CHAR_ROWS] [NUM_CHAR_COLS] =
{
//
};
Or can I miss one or both of them from the end (if I can, should I?
`extern` when applied to a variable means, "declared, but defined elsewhere". So at of the beginning of the program, it's what you should have. At the end of the program, the compiler may still allow you to use `extern` even if you provide a definition, but I do not think it's good practice and I consider it harms the readability.
About `const` specifiers. For sure I would NOT like the idea to have mismatched `const` so I'd keep them both `const`; what happens if you don't, that... depends. I have an idea but I'd need to do a little more research to confirm; in any case it's unlikely it'd pass code review :)
Awesome -- thanks so much for your help -- Max