HC08, CodeWarrior for Microcontrollers v6.3, IDE version 5.9.0 build 5292.
Using Start08.c, .prm file for project includes a "COPY" (segment? org?) in the placement section.
What do I need to change in order to allow static variables in my functions to be initialized. I'm trying to get a cross-platform library working on this target, and really need to make use of static variables if at all possible.
I'm an experienced C and embedded programmer, but very green with CodeWarrior and Freescale. Explicit instructions without too many abbreviations (WTF does feeding the COP mean in Start08.c?) appreciated.
I was given a skeleton project file as a starting point, since it's sharing the flash with a bootloader and some shared data, so using the Project Wizard is likely out of the question.
Here are the SEGMENTS and PLACEMENT sections of my .prm file:
SEGMENTS /* Here all RAM/ROM areas of the device are listed. Used in PLACEMENT below. */ Z_RAM = READ_WRITE 0x0080 TO 0x00FF; FLASH_RAM = READ_WRITE 0x0100 TO 0x01FF; /* Reserved for flash code that executes in RAM */ SHARED_RAM = NO_INIT 0x0200 TO 0x0215; /* Used to send data between bootloader and application */ RAM = READ_WRITE 0x0216 TO 0x087F; /* The rest of RAM */ /* Application areas */ APP_RESERVED = READ_ONLY 0x8000 TO 0x83FF; /* 1KB storage for app - never touched by BL */ APP_VER_DATA = READ_ONLY 0x8400 TO 0x841F; APP_FLASH_ROM = READ_ONLY 0x8420 TO 0x850F RELOCATE_TO 0x100; APP_ROM = READ_ONLY 0x8510 TO 0xF1BB; APP_VERSION = READ_ONLY 0xF1BC TO 0xF1BF; /* 4 byte pointer to app version string */ APP_IVT = READ_ONLY 0xF1C0 TO 0xF1FD; /* Application Interrupt Vector Table */ APP_START = READ_ONLY 0xF1FE TO 0xF1FF; /* Application start address */ /* Bootloader (BL) areas */ FLASH_ROM = READ_ONLY 0xF200 TO 0xF2EF RELOCATE_TO 0x100; /* Flash routines that get copied to RAM */ BOOTLOADER = READ_ONLY 0xF2F0 TO 0xFFA9; /* Bootloader ROM area */ BL_VERSION = READ_ONLY 0xFFAA TO 0xFFAD; /* 4 byte pointer to BL version number */ENDPLACEMENT SHARED_DATA INTO SHARED_RAM; _DATA_ZEROPAGE, /* zero page variables */ MY_ZEROPAGE INTO Z_RAM; FAR_RAM, /* non-zero page variables */ DEFAULT_RAM INTO RAM; APPLICATION_VERSION INTO APP_VER_DATA; FLASH_ROUTINES INTO APP_FLASH_ROM; _PRESTART, STARTUP, /* startup data structures */ ROM_VAR, /* constant variables */ STRINGS, /* string literals */ VIRTUAL_TABLE_SEGMENT, /* C++ virtual table segment */ DEFAULT_ROM, COPY INTO APP_ROM; /* copy down information: how to initialize variables */ END
Thanks in advance for any guidance.
-Tom
解決済! 解決策の投稿を見る。
The L1981 means that either start08.c is not linked (is it added to the project, and if so, is it configured to be built for the current configuration), or that start08.c is configured not to initialize variables via __ONLY_INIT_SP command line define.
Also is it a stock start08.c (compare with the one in lib/hc08c/src/start08.c)?
Another reason is the NO_INIT segment qualifier used in the prm, initialization data placed in the SHARED_DATA section is dropped as that section is placed into a NO_INIT segment (but this would still generate a copy down, just not for that those variables).
Daniel
As a temporary fix, I've updated my functions to use an initial value of 0 for the static variables. Probably a more efficient method (doesn't require ROM space for initial value), but I'd still like to know about copydown for future reference (and to initialize other structures).
The copydown fills two purposes:
- Initalizing all static and global variables to the values set by the programmer
- Making the comiler conformant to ISO C. ISO C enforces all static and global variables to be initialized to zero, unless the programmer has initialized them to another value explicitly.
This is because C was written for RAM-based Unix systems and they had not given thought to NVM-based systems back then. Therefore there are some problems with this.
First there is the obvious loss of speed caused by overhead code executing at startup. If there is a need to start the program quickly, the copy-down code isn't optimal. It does indeed take up some flash code as well.
A bigger problem is reliance on initalization of static variables. Code that implictly takes advantage of the fact that statics are initalized, as guaranteed by the standard, will most certainly cause problems. If the code is ported to another embedded compiler, it may not implement this part of the standard. And if the code is read by another programmer, they may not be aware of the static initalization rules of the C language. There is also the potential for slips, where the programmer accidently starts treating automatic storage variables in the same way, which leads to bugs in 100% of the cases.
It is also bad practice to rely on RAM values that has not been changed over long periods of time. If your program is expected to run days, months or years without break, you shouldn't rely on RAM. It is fickle by nature and I doubt you can get Freescale to guarantee that RAM values will be preserved that long. To my knowledge, there is no such data available in the datasheet.
The best solution:
- Get rid of the start file. It doesn't conform to ISO C and it was written by someone who got $1 per compiler switch he wrote. Also, while all that junk code is executed, the CPU is in a dangerous mode where watchdogs, LVI, pull-ups on ports etc aren't enabled (depending on MCU derivate). So the file is to be regarded as a hazard. On top of that the file is impossible to interpret for anyone without a built-in pre-processor in their brain. Startup has to go.
You can't remove the file from your project, or the linker will start to whine. So you might have to accept that some junk from the start file resides in flash, but will never be called or used. You can probably live with that.
- Write your own vector table in C, unless you have done this already. It is possible to do so with pure ISO C, I have posted an example of how to do so in CW somewhere on these boards.
- In your own reset vector, set the stack, watchdog, and other such fundamental stuff. Then call main from there.
This should be done for all CW projects on HC(S)08 and HC(S)12, to give a safer, more reliable and efficient system startup. Another big advantage is that the code can be made fully ISO C compliant, save for the LDS instruction which cannot be done through C.
You are free to call high priority initialization routine before C startup.
Copy-down and startup code takes much less space than explicit initalization of bunch of variables would take. On S12, typical explicit zeroout of every char variable (in user code) takes 3 bytes, initialization with non zero value - 5 bytes. Having many variables and using copy-down, zero-out of single char should cost you less then one byte per char, and static non-zero initializer should cost only little more than 1 byte per char.
Is someone using non standard compliant compilers these days?
It doesn't have to be in C. C program starts from main().
Hardware should not smoke even if MCU is frozen immediately out of reset for infinite time period. Typical BDM debug session starts in special mode, no code is executed until someone presses some button! Startup routine takes only limited time!
Pff.
Remove VECTOR 0 _Startup from PRM and linker won't mind you removed _Startup routine.
How that would be safer???
> You are free to call high priority initialization routine before C startup.
This is also what I am suggesting further below in the post.
> Is someone using non standard compliant compilers these days?
Regarding this particular example: there is very often a non-standard option "get rid of static initalization". CW has this option too, and if it is set, the compiler does not fully conform to ISO C any longer.
Generally speaking, there aren't many compilers that are compliant. I actually can't think of a single one that is. CW uses plenty of non-standard syntax. A recent analysis I did of the register maps revealed the following:
- 2 syntax cases on non-standard C
- 1 case of undefined behavior
- 6 cases of implementation-defined behavior
It needed nothing of the above to achieve the same functionality. It could have been written in pure ISO C.
> It doesn't have to be in C. C program starts from main().
Yet CW will compile that file with its C compiler, and as a programmer, you are expected to at least get a grasp of what is going on in that file. What serious person lets code inside their program that they don't know what it's doing?
> Hardware should not smoke even if MCU is frozen immediately out of reset for infinite time period. Typical BDM debug session starts in special mode, no code is executed until someone presses some button! Startup routine takes only limited time!
And what if something causes the MCU to go haywire during that time? The only sensible solution is to set various safety-related hardware register immediately out of reset.
>> Remove VECTOR 0 _Startup from PRM and linker won't mind you removed _Startup routine.
Actually there is more than that, there are various segments referring to the copy-down code inside the PLACEMENT statements. I don't remember which ones. It is possible that you can eliminate those statements though, and thereby get rid of startup entirely.
>> How that would be safer???
The main concern is the reliance of initalization of static variables, as I pointed out in the first post. As we can see from posts on these forums, CW users get this wrong -all- the time.
That would be constructive suggestion how to reduce time from reset to some critical hardware init routine. But all your post was only about negative aspects of copy-down, C zeropout and static initializers, also how bad start.c is coded despite of zilion possible compiler switches and memory models. Junk, crap, $1 per preprocessor switch.
"The Best solution" isn't the best at all, because there are similar chances of either 1) someone taking your old project will enable "get rid of static initialization" or 2) someone taking your old project will modify it expecting standard zeroout. If case 1 doesn't know what he is doing, then case 2 is expecting standard C behaviour. What's better?
Standard defined headers math.h, stdio.h etc no doubt must be pure C. Regarding derivative headers, I also don't like not portable bitfields and never use them. But @ extension allowing real variables to be placed at const addresses eases debugging. Pure C defines like #define PORTA (*(volatile unsigned char*)0x0) are preprocessed to constant addresses, while variable PORTA or _PORTA is visible in disassemble, also debugger assembler window shows it as a reference to variable istead of read/write to some unnamed address. It is possible to abandon such extensions, but then absolute assembly or something should help to put registers to fixed addresses. Maybe big C struct for whole registers block could be defined and placed at start of regs block, I don't know. But certainly we can live with @ in derivative headers., because target specific code is not portable to other target. Porting to different compiler is as simple as switching to headers provided with that compiler.
Start.c is even not listed in Sources branch. It is Project Settings->Startup Code. You can even open library source code and tell aha, I see inline assembler here!.
Typical debug session starts with haywire: nothing is initialized, CPU is halted forever. Don't you debug your code on real hardware or do you have working sensible solution?
You are right here and that's weird. I guess linker forces usage of _Startup to draw call tree in the map file. What for linker needs _startupData I don't know. I even tried to suppress map file generation putting MAPFILE NONE to PRM file. It didn't help.
There are no reliable fuses against ignorance and incompetence.
>> This is also what I am suggesting further below in the post.
> But all your post was only about negative aspects of copy-down...
This is what I wrote "further below":
"- Write your own vector table in C, unless you have done this already. It is possible to do so with pure ISO C, I have posted an example of how to do so in CW somewhere on these boards.
- In your own reset vector, set the stack, watchdog, and other such fundamental stuff. Then call main from there."
> "The Best solution" isn't the best at all, because there are similar chances of either 1) someone taking your old project will enable "get rid of static initialization" or 2) someone taking your old project will modify it expecting standard zeroout. If case 1 doesn't know what he is doing, then case 2 is expecting standard C behaviour. What's better?
This is why you should write your code so that it isn't dependent on static initalization. If nobody relies on it, neither me or the person getting their hands on my code, then there will never be any problems, no matter compiler settings.
> Standard defined headers math.h, stdio.h etc no doubt must be pure C.
There was a time when I believed so too, before I got too much nerdy in-depth knowledge of the C standard... Out of curiousity, I did just now investigate CW math.h (plus included headers from math.h) regarding compliance.
3 different violations of the C90 standard were found at brief examination:
1) #endif __NO_MATH_PROTOTYPES__
2) #define NEAR near
3) #define FAR far
None of these standard violations fills any purpose at all.
> But @ extension allowing real variables to be placed at const addresses eases debugging.
This is just there because some muppet decided: "Hey, I want to make my own operator! Now wouldn't that be nice?". This could have been resolved with #pragmas instead and there wouldn't be any standard violations.
(Or better: a target aware debugger. Ooh that would be so nice, such a debugger wouldn't munch up my SPI and SCI status registers either. Can I have one for christmas, Freescale?)
> Maybe big C struct for whole registers block could be defined and placed at start of regs block, I don't know.
This is entirely possible to do in CW, but only if you get rid of static initalization! Because otherwise the compiler will download copy-down zeroes into your registers, which might be a tad bit unconvenient. Yay, another reason to get rid of the startup file!
> Typical debug session starts with haywire: nothing is initialized, CPU is halted forever.
What if something goes haywire in the real product then?
> Don't you debug your code on real hardware or do you have working sensible solution?
I don't touch software simulators, I always debug on hardware. Whatever that has to do with anything... The sensible solution would be the initialization of registers directly from the reset vector as suggested elsewhere.
> You are right here and that's weird. I guess linker forces usage of _Startup to draw call tree in the map file. What for linker needs _startupData I don't know. I even tried to suppress map file generation putting MAPFILE NONE to PRM file. It didn't help.
I have been playing around with this too in the past, without a solution. So instead I have a merry little _startupData in dead flash space in all my products, that is never called. I guess it will at least confuse the heck out of the reverse engineers.
I still don't see anything wrong with CW _Startup file and startup data. This is how Freescale are dealing with standard C requirement for zero out and static initialization.
And this is why you shouldn't turn on "get rid of static initialization" or remove startup file.
Yes, #pragma would be standard. But porting #pragma flooded code to different compiler is also not very nice thing. Most likely you will get annoying warnings like "unknown pragma" or "pragma not handled". And to be compatible you will end with even more #if-#endif's to suppress these warnings.
Just NO_INIT in prm file.
You said previously that Startup() is slow and hardware must be enabled sooner than that. I state that hardware should not get broken or injure someone if CPU executes Startup too long or doesn't start at all. If so, then 1) It's not very important how long zero out and static initialization take time. 2) Special application requirements to initialize hardware ASAP on power on can be met executing some time critical init routine before Startup.
Hello,
In response to some of the comments by Lundin:
For the HCs08 derivatives, and without banked memory use, should ANSI initialisation be declined during the creation of the project, the Start08.c code will occupy all of seven bytes, to initialise the stack pointer and immediately jump to main(). There would be little point in not using this code.
The POR state for HCs08 devices is generally with the COP timer active, and LVD reset enabled. While the COP is not normally serviced during the initialisation process, for special cases there is provision to do so.
I could envisage that delays to the initialisation of I/O registers could be critical for some projects, and may justify the omission of variable initialisation within Start08.c in order to hasten the setup of the I/O. Additionally, the code would be executed at the default bus clock rate, which may be slower thatn the eventual operating clock rate. However, my guess is that few projects would have this degree of criticality.
I agree with Lundin about the potential reliability limit of maintaining a RAM value over an extended period, and maybe the potential need to manually re-initialise, prior to first read If there is also likely to be an extended period between successive writes the value must be programmed to non-volatile storage, and would cease to be a static variable issue.
At risk of being off-topic, I would suggest that the potential for data corruption would be even greater for the MCU registers, and particularly the I/O registers. Yet DDRs, etc. seem to be registers that many programmers "set and forget". I would suggest that these settings, and maybe pullup settings, should be re-initialised within each circuit of the main loop (along with COP servicing)
.
I can see potential issues with the initialisation of local static variables within a function, if ANSI initialisation does not occur. There would appear to be no other way of setting the value for first time use, without unnecessarily increasing the scope of the variable, and providing a separate initialisation function.
Regards,
Mac
> For the HCs08 derivatives, and without banked memory use, should ANSI initialisation be declined during the creation of the project, the Start08.c code will occupy all of seven bytes, to initialise the stack pointer and immediately jump to main(). There would be little point in not using this code.
One point would be that you might want to catch the various reset causes that the S08 supports, and handle them differently. But indeed, the S08 version of CW handles startup much more gracefully than for example the S12 one.
> The POR state for HCs08 devices is generally with the COP timer active, and LVD reset enabled. While the COP is not normally serviced during the initialisation process, for special cases there is provision to do so.
On S12 COP is disabled on startup. Also, what kind of LVD is enabled at startup? The MCU has no idea if you are running on 3V or 5V, you have to set this explicitly. If 5V LVD is enabled at reset, that is of no use to me when I run the MCU on 3V and vice versa. Having the wrong trip voltage set is the same thing as having LVD disabled.
> I agree with Lundin about the potential reliability limit of maintaining a RAM value over an extended period, and maybe the potential need to manually re-initialise, prior to first read If there is also likely to be an extended period between successive writes the value must be programmed to non-volatile storage, and would cease to be a static variable issue.
And if you are doing that, you are going to set static variables in runtime regardless. So the copy-down at startup turns redundant.
> At risk of being off-topic, I would suggest that the potential for data corruption would be even greater for the MCU registers, and particularly the I/O registers. Yet DDRs, etc. seem to be registers that many programmers "set and forget". I would suggest that these settings, and maybe pullup settings, should be re-initialised within each circuit of the main loop (along with COP servicing)
Indeed, I agree completely and I do this too. I don't have the slightest idea how these registers are implemented in hardware, so I always refresh them from the main loop just to be safe. I think one can assume that these registers are more exposed to EMI etc than the internal RAM will ever be, which would be another reason to refresh them.
> I can see potential issues with the initialisation of local static variables within a function, if ANSI initialisation does not occur. There would appear to be no other way of setting the value for first time use, without unnecessarily increasing the scope of the variable, and providing a separate initialisation function.
That's what you will have to do, it must turn into a file scope variable instead of a local scope one. But as long as the variable is not accessible from outside the source file, this is a minor concern. Each source module will most likely have an init function anyhow, so you can initialize the variables from there.
If you are using the refresh method you are suggesting, such an init function can be called repeatedly from the main loop, each time before the code module is to be used.
Hello,
After considering the various arguments put forward in this thread, my conclusion was to stay with the use of the Start08.c startup code, as is, certainly for HCS08 derivatives. It seems that the only issues would be that:
The first issue would be accommodated by the use of an additional NO_INIT segment in the PRM file. The prestart code can be accomodated with a modified main.c framework - the following framework code would appear to operate in the required manner, certainly for HCS08 derivatives.
#include <hidef.h> // EnableInterrupts macro#include "derivative.h" // Peripheral declarations#include <start08.h>void main(void){ EnableInterrupts; for( ; ; ) { __RESET_WATCHDOG(); } // loop forever}#pragma NO_ENTRY#pragma NO_EXITvoid prestart( void){ // Prestart initialisation code _Startup(); // Copydown process}
The use of this framework requires that the reset vector point to prestart, rather than to _Startup, by modification of the PRM file entry, or alternatively the vector table, if this is used. If prestart code is not inserted, the additional overhead is only three bytes of code (JSR _Startup instruction).
Note that, for a POR of an HCS08 device, the stack pointer is initially set to the top of zero page RAM. This should usually be satisfactory for the prestart sequence. Then when _Startup is executed, the stack pointer will be re-positioned to the PRM specified location.
The following test results were obtained for use of the standard Start08.c code:
Code size with copydown enabled: 132 bytes
Execution with no static variables present: 171 cycles
Four static variables then created; two with 8-bit size and two with 16-bit size.
Zero out of variables: 289 cycles
Non-zero initialisation of variables: 764 cycles
Two static 8-bit arrays were then additionally created, with zero out required, and with non-zero initialisation of the previous variables.
Array size 100 elements: 2764 cycles
Array size 200 elements: 4772 cycles
As can be seen from these results, the zero out process is much faster than non-zero initialisation of a variable. It does not seem to matter whether the static variable is explicitly set to zero, or there is implicit zero out. If possible, large static arrays should be placed in the NO_INIT category - probably OK for SCI buffers, etc.
In response to a specific issue previously commented by Lundin:.
> The POR state for HCs08 devices is generally with the COP timer active, and LVD reset enabled. While the COP is not normally serviced during the initialisation process, for special cases there is provision to do so.
On S12 COP is disabled on startup. Also, what kind of LVD is enabled at startup? The MCU has no idea if you are running on 3V or 5V, you have to set this explicitly. If 5V LVD is enabled at reset, that is of no use to me when I run the MCU on 3V and vice versa. Having the wrong trip voltage set is the same thing as having LVD disabled.
With the implemtation of prestart code, this is where the COP could be enabled for the S12.
For HCS08 derivatives, the LVD module will commence with a default setting of 3V after a POR. Since the default bus frequency is supported at 3V operation, this initial setting should be satisfactory for either 3V or 5V operation. The LVD module is automatically utilised duing the POR sequence in cases where the Vdd ramp-up is slow, by holding a reset state until Vdd reaches a satisfactory level.
Regards,
Mac
Hello Tom,
It is probably easier to use the project wizard to create your CW project, and then add your existing source files to the project. Additionally, your specific PRM file would need to replace the default one.
When you create the project you need to stipulate whether you require ANSI initialisation of variables, or no initialisation. I assume that this provides the correct configuration for start08.c file to enable the copydown function. Static variables that do not have an initial value defined would be zeroed. The project wizard also requires that you stipulate whether you require floating point operations.
COP = Computer Operating Properly, in other words a watchdog timer that need to be periodically cleared to prevent a reset should it timeout.
Regards,
Mac
The L1981 means that either start08.c is not linked (is it added to the project, and if so, is it configured to be built for the current configuration), or that start08.c is configured not to initialize variables via __ONLY_INIT_SP command line define.
Also is it a stock start08.c (compare with the one in lib/hc08c/src/start08.c)?
Another reason is the NO_INIT segment qualifier used in the prm, initialization data placed in the SHARED_DATA section is dropped as that section is placed into a NO_INIT segment (but this would still generate a copy down, just not for that those variables).
Daniel
I have the stock start08.c linked in the project.
And I just found how to edit the Standard Settings for the project, and I see the __ONLY_INIT_SP macro being passed to the compiler.
Thanks for helping me track that down!