Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Comments on How to perform initialization of static storage variables in embedded systems?

Parent

How to perform initialization of static storage variables in embedded systems?

+5
−0

Whenever declaring a variable in C outside a function at file scope or when specifying it as static, it gets assigned a life time known as static storage duration. Meaning it will be accessible throughout the whole execution of the program.

Such variables are typically allocated in one of two RAM segments most often named .data or .bss:

  • .data is used for all static storage duration variables that were explicitly initialized by the programmer to a value other than zero.
  • .bss is used for all static storage duration variables that are initialized to zero.

Also as per C language rules, any static storage duration variable which is not initialized to a value explicitly, gets implicitly initialized to zero and therefore ends up in .bss. In case of pointers, they are initialized as null pointers.

On a PC/hosted system, everything including the executable code is simply loaded into RAM at once by the OS. But in case of microcontrollers we have no such thing and often prefer to execute code out of flash/non-volatile memory.

When starting up a new software project in any microcontroller IDE, I am often given an option "minimal/fast start-up" versus "ANSI/ISO/standard start-up". This seems to refer to if these initialization rules should be used or not.

How are these variables initialized in case I pick "minimal" start-up? What are the advantages and disadvantages of this feature?

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

Post
+5
−0

Things happening/things we want to happen before main() is called:

The code executed before main() is often referred to as "the C runtime (CRT)" or just "start-up code". This code is typically provided by the microcontroller-specific libraries and among other things, it initializes .data and .bss - since the C standard requires that static storage duration variables are initialized at some point before main() is called. This is carried out by copying a chunk of initializer values from flash into .data, sometimes referred to as "copy-down", or in case of .bss, the CRT simply sets the whole region to zero.

This initialization potentially takes a lot of time from the point where the microcontroller is started until main() is called. Be aware that some dysfunctionally written CRTs do not set-up funamental things like watchdog and clock settings before running the initialization code, but relies on the application programmer doing that from main(). This is the wrong way to do things - it is both dangerous and also potentially needlessly slow. Suppose that a CPU/software error happens during start-up - then we want the watchdog to be enabled. Or suppose the MCU is running on a slow internal oscillator out of reset, but we would like to use an external one or set up some PLL/FLL, in order to speed up the system clock. If so, we would naturally like to do that way before running the whole .data/.bss initialization.

Either way, there might be lots of other reasons as well why we don't want a lot of slow start-up code executing out of reset:

  • The application might need to perform something quickly when it starts, or
  • In case we encountered critical errors during previous execution that merited a MCU reboot, we might want to get back up running as soon as possible, or
  • In case we are writing safety-related software, RAM could be considered too unreliable to hold default values for long time periods, unless continuously refreshed from non-volatile memory. There could be any amount of time passing from the point where the variable was initialized until it was first used.

Minimal/fast start-up

The minimal start-up option provides a way to skip all initialization and thereby create a significantly shorter start-up time. There are disadvantages of using the minimal start-up, however. First of all, our static storage duration variables end up not initialized. At all. Not to a specific value, not to zero. We have sacrificed C language standard compliance in favour of execution speed.

So if we have a file scope variable declared as int x=5;, we can't actually know what value it has - it is not 5 but indeterminate. If we aren't aware of this, minimal start-up is obviously very dangerous.

Whenever a minimal/fast start-up option is used, variables with static storage duration must be initialized in run-time!

"Run-time" as in not by the CRT, but by the application program. This means that in the above example we should just have declared int x; and then later inside some init() function assign x=5;. Every single variable in the program must be initialized in run-time before use.

Now as it happens, every module in our program is likely to have some initialization function anyway. For example any driver for a hardware peripheral will likely need to initialize a bunch of hardware registers before it does anything else. And then it might as well initialize all variables used by that driver at the same time. This way we move the initialization execution time away from start-up and into the specific code module where the variable is used.

This is the correct way of initializing such variables anyway, because of the previously mentioned "RAM is considered unreliable" reason. Sure, modern microcontroller RAM is way more reliable than what it used to be some decades ago. But like with any electronic component, it is also subject to EMI. And in case of RAM specifically, it is also sensitive to cosmic rays. Plain software bugs is a far more likely reason for RAM corruption however, so not relying on default initialization values is also a method of defensive programming.

(Similarly, executing code out of RAM is considered bad practice on high reliability systems, at least not without error detection mechanisms like ECC and/or CRC checksums.)

Summary

It is considered good practice in any embedded system to never rely on default or explicit initialization of static storage duration variables.

Doing so enables the option for using a minimal start-up, including portability to compilers that may use such a start-up. But it also makes the variables somewhat less vulnerable to problems like EMI, cosmic rays or plain bugs. It is perfectly possible to write fully standard compliant C code still - the minimal start-up makes the CRT non-compliant, not our own source code.

Also, relying on implicit initialization of any variable is considered bad practice, for example static int x;, simply because it is confusing and sloppy. Did the programmer intend to zero-initialize this variable or not? Writing static int x = 0; is self-documenting. Now of course the programmer could also have been an embedded systems veteran who adhere to the best practices default initialization mentioned above and therefore left out the initializer for that purpose.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

2 comment threads

Minimal CRT startup code is not dysfunctional. (7 comments)
Some details to also consider (2 comments)
Minimal CRT startup code is not dysfunctional.
Olin Lathrop‭ wrote almost 2 years ago · edited almost 2 years ago

Nice writeup (+1), but I can't let this go: "some dysfunctionally written CRTs do not set-up fundamental things like watchdog and clock settings". There are reasons you don't want to use a watchdog timer, or need more control over the clock settings than can be explained to the CRT. Good CRT startup code is often what does the minimal required work, then gets out of the way as quickly as possible.

If enabling a watchdog so early on is important, then use a microcontroller where the watchdog can be set up by the static configuration in ROM.

Lundin‭ wrote almost 2 years ago

Olin Lathrop‭ Yeah sure but the normal use is: you wish to use the watchdog, you wish to use the clock quartz which you provided, you wish to enable LVD/brownout detect, you wish to set GPIO port directions and pull resistors etc etc. I once wrote a summary on how to do this correctly here: https://stackoverflow.com/a/47940277/584518. And the sheer amount of completely dysfunctional embedded C programs out there means that this cannot be stressed enough: using the out-of-the-box CRT provided by some silicon vendor or ARM is dangerous and naive.

Lundin‭ wrote almost 2 years ago

On classic microcontrollers you can often just "hack" the CRT by replacing the reset vector with a custom one, execute all critical code, then call the CRT and let it do its thing, and then the CRT eventually calls main().

Olin Lathrop‭ wrote almost 2 years ago · edited almost 2 years ago

" you wish to use ...". I disagree this is the "normal" case. The CRT startup code shouldn't be making such assumptions. Lots of problems happen with hidden logic tries to automatically do things for you. You should get what you explicitly ask for. If you don't ask for something you need, that's a bug. The other way around where you get stuff you didn't ask for can also be a bug, but much harder to find and unintuitive to fix.

Lundin‭ wrote almost 2 years ago

Olin Lathrop‭ Well in case of .data/.bss initialization, the compiler by default has to perform it because it claimed to be C standard compliant. Regardless if the user asked for it or not. And so they end up with a solution which is standard compliant but unlikely to be of use to any single user. Or in case of some ARM CRTs, they set up the clock to run on the internal RC oscillator which is slow, then grind through the whole initialization after that. The vast majority of all embedded systems definitely should set the things mentioned in the above link before they do anything else. I have a hard time picturing a system which does not need to set any of the mentioned things as early on as possible.

Bruce Hansen‭ wrote almost 2 years ago

I'm with Olin on this one. With an embedded microcontroller, you don't want the CRT making a bunch of assumptions about how the device will be used, and configuring it all for you. In the case of ARM CRTs which you raise - how can the CRT even 'know' if any other clock configuration is even possible? If your hardware doesn't have a crystal or external oscillator then the internal HSI RC oscillator is all there is. If your hardware is particularly power sensitive you might not even want to switch on the PLL. If you're running from some kind of micro-power energy harvesting supply then you really might only want to wake up from deep sleep periodically, check a couple of things, and then go straight back to sleep, all the while consuming as little power as possible.

Lundin‭ wrote almost 2 years ago

Bruce Hansen‭ I never claimed that the CRT should be doing all these things for you. But it needs to provide a location where you can place such code. For example by exposing the reset vector in a separate file so that it can be changed by the application program without having to modify the CRT code itself.