I've written a few 8bit embedded systems and the codebase I inherited and have expanded is basically 80% global variables (extern volatile), and than non-global control flags and logic variables as needed.
The end result, is that you end up with a lot void() functions to modify the global variables.
The systems run fine and the software quite readable and easy to work on, but I always have that design nag in the back of my head that I should be refactoring everything and pointer'ize the next design.
I don't have any religiosity about the memory usage aspect, the static memory is there to use as much as the heap is. It's more a question of as systems get larger, I suspect maybe the globals start to be more of a hinderance than you think? I've never gotten there in program size.
Do many of you experienced embedded software programmers use pointers extensively in your 8bits systems?
I can think of three possible use cases for using pointers in C51: linked lists (which we don't use) in this system, structs in place of independent variables and then functions with pointers to modify the strucs, maybe a double pointer if you absolutely need to modify a pointer but I can't fathom a situation in building a consumer product with an 8051 (maybe building an OS...).
I get that the functions can get re-used if you pointer'ize your code, but in one sense the functions are pretty trivial and one off in the embedded systems I've built. Very application specific stuff.
A couple of you guys who are still around C51 forum, what is your most compelling (or not compelling) use case for pointers in the code you have released?
I stopped C51 (aka 8x51) development many years ago now and, where it is practical, I would always suggest you move from that outdated architecture to something current.
Pointers in C51 are extremely inefficient. As a consequence, dynamic memory allocation in C51 is extremely inefficient. I avoided both wherever practical. I know at least one person on this forum that would disapprove when I say that I frequently used the LARGE memory model. For our systems it worked incredibly well, since we had to store relatively large amounts of data at low-ish speed. But I still avoided dynamic memory allocation and pointers wherever possible.
I do wonder with what you describe though. You say you have 80% global variables but use void() functions to modify them. Why?
If they are really global variables, you don't need the overhead of functions to access them. You probably mean local variables to a module.
At the risk of receiving plenty of "that's not the way to do it", what we did was have many true global variables that could be manipulated without access functions, but named them in such a way that the owning module of the data items was clearly apparent. This reduced the need for the overhead of access functions.
This was only something that we considered 'acceptable' because of the limited platform capabilities. All developers of the projects were aware of the agreed rules. And we all stuck to them, so the code was relatively easy to maintain.
As I suggested at the start, if possible try to move your developments across to a newer architecture. We went the ARM route (with Keil tools) and have not regretted it. You can then use pointers and dynamic memory allocation without worrying about their impact. It's been great.
Doing this will also allow you to have coding standards that are far more typical.
@Neil -- that's some really helpful insight. Yeah sadly, we build some really cheap products so 8051s are what's in the budget.
For the global variables, I think your described approach is kind of similar to what I have.
We might have some really useful global ArrayX[] that three functions modify in some form.
I guess the only actual reason we use functions on the globals is to try to modularize the code a bit, and try to conceivably reuse the code in some fashion.
Those modules are topically grouped in different .c files to try to build some kind of reusability. (For example, I have some CRC routines I split out to crc.c), it has references to global variables but if you copied those variables over to a new project you'd have a working CRC routine pretty quickly).
Is this your point to a degree, if you really get down to it, if you have absolute globals why even use the overhead of functions?
I agree it's actually a bit timely to even re-use those scraps of code. Now if you had pointerized the functions, they are more re-usable.
But in the case of my CRC function, how many times am I going to pass a 5 byte array, and do the calculation, and dump the CRC checkcode in the 6th byte.... The software I end up having just seems so narrow.
From a refactoring standpoint, what is not good is that we didn't at least use a "g_" or some denominator to at least call out what the global is associated with.
----
What sort of particulars are at play when you say that pointers / dynamic memory allocation is inefficient with C51? That really cuts to the heart of the matter.
Because I totally see more updated ARM Cortex M code, like middleware (the Nordic SDK comes to mind) is really heavy with strucs and pointers.
Yes, given that these processors are relatively slow, I see little point in giving them extra work to do just so I can appease the code style police. This is not an argument I would give on newer, faster or more capable processors. It's simply a case of realism and knowing the limits of the processor being used.
The 8x81 is an 8 bit processor, and a limited one at that. It always amazed me that Keil (and others) managed to make a compiler that complied so well with the C standards.
That said, for each line of C code, the compiler has to produce the corresponding assembly code. Even for simple operations, the code produced can be lengthy and require significant cpu cycles at run time.
The original 8051 only has one 16 bit pointer, so it meant that using pointers requires a lot of data shuffling.
Using dynamic memory allocation requires quite extensive use of pointers, so that inefficiency can be multiplied.
Not so nice.
The end result, is that you end up with a lot void() functions to modify the global variables. HUH??
The systems run fine and the software quite readable and easy to work on, but I always have that design nag in the back of my head that I should be refactoring everything and pointer'ize the next design. in my opinion, the one that stated "global variables should be avoided" did not have an inkling about efficiency
Do many of you experienced embedded software programmers use pointers extensively in your 8bits systems? I do not know of 'many' I don't. a rarely mentioned fact is that, both fro design and efficiency, arrays are more efficient. A compiler can, in some cases detect that you overrun an array, for a pointer such is impossible
linked lists here, again, you go for maximum inefficiency
A couple of you guys who are still around C51 forum, what is your most compelling (or not compelling) use case for pointers in the code you have released? as echoed below, pointers are efficiency killers
from reply: I stopped C51 (aka 8x51) development many years ago now and, where it is practical, I would always suggest you move from that outdated architecture to something current. I disagree with 'always' of my last 10 designs, one was a '51 simply because it was the most efficient choice. af far as 'outdated' e.g. SiLabs regularly introduce new variants.
Pointers in C51 are extremely inefficient. As a consequence, dynamic memory allocation in C51 is extremely inefficient. I avoided both wherever practical. agreed above
I know at least one person on this forum that would disapprove when I say that I frequently used the LARGE memory model. yes, me and I wonder how you can advocate efficiency above and te advocate the most inefficient method here
I do wonder with what you describe though. You say you have 80% global variables but use void() functions to modify them. Why? see 'HUH' above - and not explained in your reply
At the risk of receiving plenty of "that's not the way to do it", what we did was have many true global variables that could be manipulated without access functions, but named them in such a way that the owning module of the data items was clearly apparent. This reduced the need for the overhead of access functions. I applaud 'clarity'
that's some really helpful insight. Yeah sadly, we build some really cheap o 8051s are what's in the budget. unless they are small, some ARM variants may beat the price. I abhor the "do not use the '51 any more' as well as trying to use a '51 with paging and who knows what when e.g. an ARM would do it seamlessly
Regarding the globals and voids comment, I'm looking through something recently and I guess that is over-stated (the older code I refactored uses that technique more).
But in terms of code modularity, surely you guys break up different functionality into separate functions or .c files (do you call different thematically grouped .c files "modules" ?).
//Initialize the system again SystemOn(); //Turn on the LCD again LcdOn(); CalcBattLevel(CalcLevel); SegmentOn(BATTERY_FULL);
Some of those functional calls don't need the function, who knows it may be a wash with the compiler since they are just voids doing stuff that it could eliminate the functional call completely and just copy the instructions required.
I've been pretty taken with just using a SysTick type interrupt for running the system. Every 5ms, the interrupt triggers to poll buttons, blink lights, or do whatever.
I would say if you examined my code, I probably overuse full scope globals and I could probably switch some to file-scope. A couple of those globals really need full scope because I break out some related functions in a different .c file that acts on it. That's once place, you could see why the pointer maybe is better is that if you made all the supporting libraries designed to take pointers, the next time you go to use them you don't have
I've had a free week, so I have been brushing up on my pointer kungfu. You really start to see HOW you could pointerize your design.
I see a lot of C code using structs and the "->" operator, so the struct isn't copied onto the stack when you want to modify it.
It doesn't make sense to,char x = 1; char *ptr_x = &x? Now we are using two chars worth of memory, for the sake of just having the address of X stored in the pointers. So our functions that take a pointer as a parameter can act on X...?
I have seen in some textbooks, developers building state-machines and command launchers using pointer functions. I can see the efficiency gain there. So they can add functionality in hard typed arrays.
Trust me, I have no desire to use a linked list. I can't even fathom why you would want one in an embedded design for a small product. All of the data I've ever processed has a finite defined length that it suited for an array.
I'm happy with an 8051 (coming from a PIC) for our most recent build. The Asian brands 8051s are wicked cheap. SiLabs 8051s are "pricey" in my book!
I'm a one man band cranking software out, so I appreciate hearing from others!
I see a lot of C code using structs and the "->" operator, so the struct isn't copied onto the stack when you want to modify it. if it was the changs would be lost on return and your memory would (probably) overflow
ayhow, based on how/what you write let me tell you 20 years ago I wrote 'simple' 'C" I knew no more later I wrote 'fancy' C because I learned that Now i write 'simple' 'C' because it is maintainable
you may want to skip the middle stap
@Erik -- With my pointer kungfu studies, I am really starting to get the static/stack/heap memory divide. With more knowledge, comes more hand-wringing....
Do you take the time to really nitpick your global usage to prevent scope creep? I gotta be honest, if it costs $100/hr for design work, seems pretty low value to have a guy just nailing down the scope of every variable to a T.
I get that a static declared variable, lives in the static memory, so for all purposes it is a "global" but it has some extra protections.
Like you could really spend some time and change globals as static variables, and really drill down and try to protect variable scope... As a one man band, the probability that I am going to invardently jank up some mission critical global variable or something like that is highly improbable. But in a team of folks... maybe?
Do you take the time to really nitpick your global usage to prevent scope creep? no, with CLEAR variable names and employees that did not get their diploma by copying from the net no scope creep happens. That said a lot of 'global' variables can and should be declared static to limit the scope to the module they are in. however if a variable necessarily has to be used in more than one module I will declare it global
an example
static int ralph; void write ralph(int value) { ralph = value; }
makes ralph just as global as declaring it as a globa
I get that a static declared variable, lives in the static memory, no 'static' just limits the scope
now o clear a possible confusion 'global universals' such as int temp1 int temp2
should lead to firing the creator. No global should have more than one use.
FOR POSTERITY
Holy treasure trove batman:
ftp.ti.com/.../C51Primer.pdf
Have you ever seen this C51 Compiler guide, from 1996! Somehow this thing is living on a TI FTP server. Talk about a dream resource for C51.
This answers almost every question I've ever had (though sadly is a little silent on the pointer efficiency front!).
I see the company now does paid consulting for embedded design, but holy cow this PDF is the best thing I've ever read in the last 18 months when thinking of how to improve my code on the 8051.
They literally lay out everything from guys going from Assembly to C. So yes, they do actually explicitly explain the memory model. I get what you guys would debate on that front now. In my case, programs are so small that we aren't anywhere near resource constrained where we have to think of anything but the SMALL memory model.
I have to say it does seem to support the keep-it-simple approach.
Have you ever seen anything equivalent that this is detailed and conversational for the the ARM MDK? I find the KEIL documentation that is available online nowhere near as accessible and well written.
acceptable if not taken religiously
as most such it is a bit dated. e.g. it does not mention IDATA.
That it (due to date) mentions max 8K and then start discussing "pseudoRTOS' is a bit silly. If you need a (pseudo or not) RTOS (more think so than should) it is a grave mistake to choose the '51.
I would still recommend learning C on the PC and then adapting to the '51
we aren't anywhere near resource constrained where we have to think of anything but the SMALL memory model.
oddly enough one should not consider the LARGE model for a large code, the fact is that you are more likely to get by with the LARGE model for a small program.
One more thing, and I'm out:
I am sitting around playing with pointers this afternoon and profiling pointers. I think this probably puts the pointer question to rest for me.
#include <MCUHeader.h> struct Lion { char Age; char Name; char Weight; }; void SetAge(struct Lion *Lion, char age) { Lion->Age = age; } void main(void) { char x; struct Lion Leroy; for (x = 0; x <10; x++) { Leroy.Age = 5; } x = 10; for (x = 0; x <5; x++) { SetAge(&Leroy, 10); } }
I was messing around with using the Setter function with the Lion struct vs. accessing the struct member without the function.
////////////////////////// 5497 Cycles w/ Struct Init for (x = 0; x <10; x++) { SetAge(&Leroy, 5); } //////////////////////////
3857 Cycles w/ Struct Init for (x = 0; x <10; x++) { Leroy.Age = 5; } //////////////////////////
That's like a 30% performance hit to use the Setter function! I'm not super familiar with the compiler, maybe you can tweak that performance with an inline function. (God, I realize I've become an embedded developer, if I'm freaking about the function overhead now).
WOW. I was not expecting a penalty like that, which goes back to Mr. Burmans point.
(Keep in mind, I didn't start using these MCUs with assembly...!)
Erik -- I totally hear you on the RTOS on an 8051, at least on these cheap Asian MCUs the power consumption is atrocious compared to the PIC's and ARM Cortex M's I've used. I am looking through a modern RTOS source code, and can't even fathom running it on the C51 for our products.
We'll just program the bare metal interrupts and get the timing right... I'm not building nuclear missile guidance systems on a $0.20 MCU from Taiwan...
My #1 goal is to put these 8051s back to IDLE mode AS SOON POSSIBLE, and put that power pig to sleep.
With prices of MCU generally in free fall, and an abundance of computational power made available by Cotex M machines, it makes little sense to me to build new products using a highly limited platform that is both outdated and out-performed. Choosing such a chip at the heart of a product nowadays makes no sense as it offers no growth capabilities as many system require. A Cotex M0 will outperform any 8051 with its hands tied behind its back...
it makes little sense to me to build new products using a highly limited platform that is both outdated and out-performed. true - in many cases - but have a look at a '51 ISR with 'using' and with a 8051f120 at 100 MHz you get less than half the response time of a 166 MHz cortex. now, of course if that ISR is to do a lot, things may reverse.
I disagree with using the '51 where it does not fit (I use cortex a lot) but the '51 should not be thrown out of the toolbox.
for a small job you can have the whole '51 project running before you get the cortex BSP done.