EmbeddedRelated.com
Forums
Memfault Beyond the Launch

books for embedded software development

Started by Alessandro Basili December 12, 2011
Hi Simon,

On 12/16/2011 10:51 AM, Simon Clubley wrote:
> On 2011-12-16, Don Y<not.to.be@seen.com> wrote: >> >> Using unsigned's for counts (can you have a negative number >> of items?). Using relative measurements instead of absolutes >> (e.g., "worksurface is 23 inches from reference; position of >> actuator is 3.2 inches from edge of worksurface" contrast with >> "worksurface is at 23.0, actuator is at 22.5 -- oops!") > > I strongly agree about the unsigned int issue. _Every_ integer I > declare in C is unsigned unless I actually need a signed integer.
<frown> I try to declare types for damn near every (ahem) "type" of datum I use. I never use bare "int"s -- "signed" or "unsigned" is explicit in each typedef. There are times when I need a signed datum. But, there are also lots of times when I *don't*. The problem lies in using a data type that makes "troublesome" values possible. I also try to use control structures that make certain aspects of the data "more obvious". E.g., if "count" is expected to be a nonzero value, you are MORE likely to find: ASSERT(count > 0) do { // some stuff } while (--count); instead of an equivalent for()/while() loop. (i.e., I've just disciplined myself to see this mechanism as "you ARE going to do this, at least ONCE" whereas a for/while loop forces me to think about whether count *can* be zero going into the loop.
> I find that the number of unsigned integers in my code is _vastly_ > greater overall than the number of signed integers. > > Personally, I think C should have made unsigned integers the default.
I think C should have required the signed/unsigned syntax. Why are ints different than chars (also an integer data type)?
> BTW, on a related data representation note, another thing I like to do is > when I build something like a state machine is to start the numbers > assigned to the state symbols at value 1, instead of value 0 so that I > stand a greater chance of catching uninitialised state variables.
I tend to build state machines by tabulating the state tables: state_t states[] = { ... &some_state &someother_state &yet_another_state ... } #define NUMBER_OF_STATES (sizeof (states)/sizeof (state_t)) Then, explicitly setting the initial state to correspond with <whatever_state>. state_t is then more of an enumeration. I do this because I support "special next_state encodings" like SAME_STATE, "POP_STATE", etc.
Hi Mel,

On 12/16/2011 6:58 AM, Mel Wilson wrote:
> Alessandro Basili wrote: >> Since in this last case I was kicking the dog with this flag, I actually >> couldn't care less if I lost an interrupt as long as the period is >> enough to keep the dog quiet. But I got discouraged by a post on >> comp.dsp which stated: "This is embedded ABC basics: don't kick a dog in >> the interrupts." but no motivation was given. > > (I realize this is just a sidebar in a much more interesting post.) The > motivation would have been: you should reset the watchdog in some very high > level process that truly reflects that the processing is being done.
Hi level but low(er) priority. I.e., you want that process to run when you *know* the higher level process that is really running the machine" has been able to get its work done. I.e., having the highest priority process perform this duty just substitutes a jiffy-driven task for "the jiffy ISR"! :-/ (figuring out where to stroke the watchdog in any particular system can be tricky -- you want it to indicate that the system *appears* to be working without forcing it to have a special role)
> Resetting the watchdog on, say, a timer interrupt keeps the watchdog happy > even if the only facilities working are the timer and the interrupts. The > rest of the processing could have completely fallen off the rails, and the > watchdog wouldn't reset the system. > > You can see this happening on a desktop sometimes when the OS has become > wedged, nothing is being processed, but the little arrow on the screen still > moves when you move the mouse.
Hi Alessandro,

[I think I may have to split this into two replies due to
its length :< ]

>>> 2. "unstable"; after few hours of operation in non-compressed mode the >>> software hangs and a hardware reset is needed.
> The DSP program has a bootstrap code which loads a very minimal utility > program that we call "loader". The loader is capable of executing few > commands like "write flash", "read flash" (that we use to upload new > software) and "jump to main", where "main" is our main application.
Bootstrap resides in ROM? And, copies the loader into RAM for execution? (Or, is loader also executed from ROM?) And, once the desired executable image is "loaded", the space consumed by the loader can be released for other use? (i.e., why is the loader still resident after the main application has started?)
> After few hours running in the main, it looks like the hardware goes > back to the loader, as if there was an hard RESET that would bootstrap > the loader. In the main program there is no intention to jump back to > the loader, that's why this looks a bit strange.
But, is it in the loader *acting* like it should when normally entering the loader? Or, does it look like it "jumped" into the loader at some random spot? I.e., is the loader waiting to be commanded to load an image? Could the code have executed past the end of physical memory? Could a return address on the stack have been corrpted? (Or, stack protocol violated so you're "returning" to a *data* value instead of an address) Can you modify the loader so that it verifies certain things that it *expects* to be true when it is initially CORRECTLY invoked and then see if the bogus loader invocation can detect that these "things" aren't as they should be? I.e., let the loader notice that it is executing when it shouldn't be and take some remedial action.
>>> 4. full of logical flaws; synchronization problems are the most common >>> mistakes, but interrupt service routines are excessively and needlessly >>> long, essentially preventing any time analysis. >> >> Synchronization problems can be avoided with a disciplined design. >> This would also help identify cases of priority inversion. > > I am actually very sensitive to this problem. I do believe that for > these kind of applications the complexity of multi-tasking or > multi-threading is not necessary and a simple hierarchical state machine > may get the job done, but since I have to serve the serial port in a > timeliness fashion I'm not quite sure I would control the timing of the FSM.
I don't know how critical your timing is -- how tolerant you would be of ISRs. When it comes to serial ports, I usually implement an Rx interrupt -- so that I don't have to worry about getting around to polling the receiver often enough to avoid losing a character. (this depends on what sort of incoming traffic you have to accommodate and how quickly you can get around the jobstack). The Rx ISR pulls the data out of the receiver AND the "status" (error flags) and pushes this tuple onto a FIFO. [This assumes I need an 8-bit clean interface and can't unilaterally decide how to handle errors *in* the ISR. E.g., if the application layer wants to see the errors and possibly reconfigure the interface like autobauding. If I only need a 7 bit channel, I can cheat and pass 0x00-0x7F values to the FIFO whenever there are NO errors. And, a 0x00-0x7F value followed by a 0x80-0xFF value that represents the error codes associated with the previous datum. This allows the FIFO to appear larger when there are no errors.] If the FIFO ever fills, I push a flag into the FIFO that indicates "FIFO overrun" -- much like the "receiver overrun" error flag. This lets the application determine where gaps are in the data stream. It also lets me see if I've got my FIFO sized well. In other words, I have preserved all the information in the FIFO so it just gives me some temporal leeway in *when* I "process" the incoming data. If you have to support pacing, this gets marginally more complicated. But, the point is to avoid doing anything other than keeping the *link* running. Any processing (analysis) of the data is done elsewhere. If I have lightweight event support, I signal a "data received" event so any tasks waiting on incoming data are awakened (made ready). [You can also put a tiny FSM in the Rx ISR so that the event is only signalled if it *needs* to be signalled -- because the ISR is in the "event not yet signalled" state. This lets you trim that system call from the ISR] There is more flexibility in how you handle the Tx side. At one extreme, you can poll the transmitter and pass one character at a time out whenever you have time to check it. I usually have an FSM in the Tx ISR. When there is nothing to tranmit, the Tx ISR sits idle -- with the Tx interrupt disabled. Whenever anyone wants to send data, the data is appended to an outgoing FIFO. Again, if I need 8-bit clean channel *and* I need support for BREAK generation, pacing, etc., then I pass a tuple that tells the ISR what needs to be done. A task monitors this FIFO and, if anything is present in it, "primes" the transmitter with the first character and enables the ISR. Thereafter, each IRQ pulls a character from the FIFO and passes it to the tranmitter. When the Tx FIFO is found to be empty, the ISR disables itself and signals an event to alert the "primer" task. This allows the transmitter to run "flat out" when necessary. And, it is easy to throttle the transmitter without making big changes in the code (e.g., force the ISR to shut itself off after each character so that the primer has to restart it).
> I personally believe that interrupts should be setting flags and that's > it, in this way the synchronization is totally handled at the FSM level > and I shouldn't chase funny combinations of interrupts occurring in > various moments (how would I test that???).
Coming from a (discrete) hardware backround, I am a *huge* fan of FSM's (and synchronous logic)! You can implement FSMs in a variety of ways with varying resource consequences. I like FSMs to be driven from above. I.e., there is a state machine *mechanism* which receives "events" and uses "transition tables" to determine how each event is handled, the state into which the machine should progress when processing that event *and* how to process it (i.e., "transition/action routine"). For a user interface, these state tables might look like: State NumericEntry { On '0' thru '9' go to NumericEntry using AccumulateDigit() On BACKSPACE go to NumericEntry using ClearAccumulator() On ENTER go to PopState using AcceptValue() Otherwise go to SameState using DiscardEvent() } AccumulateDigit(event_t *digit) { accumulator = (10 * accumlator) + *(int *)digit; acknowledge_event(digit); } etc. [This should be fairly obvious. The only tricks being the SameState keyword (which says "stay in whatever state you are in currently") and PopState keyword (which pulls a state identifier off of the "FSM stack" and uses that as the next state -- this allows a set of state tables to be used as a subroutine)] This sort of table can reduce to as few as 4 bytes per entry. If you eliminate support for the "thru" feature (i.e., force anentry for *every* possible event), then you can cut that back even further. And, since the processing is trivial, all of the entries can be examined very quickly (you would put the most frequently encountered events in the earlier entries). In ASM, this can be fast as greased lightning (i.e., *in* an ISR). Note that this example is treating keystrokes (conveniently named with the character codes they generate!) as "events". I could modify this: State NumericEntry { On '0' thru '9' go to NumericEntry using AccumulateDigit() On BACKSPACE go to NumericEntry using ClearAccumulator() On ENTER go to PopState using AcceptValue() On BarcodeRead go to PopState using ReadBarcode() Otherwise go to SameState using DiscardEvent() } ReadBarcode(event_t *event) { accumulator = get_barcode(); // overwrite any keystrokes acknowledge_event(event); } etc. Note that each transition/action routine (function) explicitly acknowledges each "event" that it "processes". This allows a transition routine to *propagate* an event - by not acknowledging it so that it remains pending. But, more importantly, it provides a means by which the FSM can signal the "event GENERATOR" that it has been processed. This gives you flexibility in how you implement those events. E.g., an event can be as simple as: event_t ThisHoldsTheEventsForTheFSM; Anything wanting to signal an event can spin on this variable and, once verified to be "NO_EVENT", feel free to stuff *their* event code into it. Then, keep spinning waiting for the value to *change* (back to NO_EVENT *or* some OTHER event!) which signals that the event has been recognized and acted upon. (e.g., when BarcodeRead has been acknowledged, the barcode reader could re-enable *itself* -- so the application doesn't have to be aware of this detail/requirement) [you can also prepare lists of event_t's that the FSM scans so that each event generator has its own event_t through which it signals the FSM] Since an event can come from *anywhere*, you can provide: UART_Receiver.c event_t UART_Receive; // fed by Rx ISR UART_Transmitter.c event_t UART_Transmit; // fed by Tx ISR CCD_Handler.c event_t CCD_Ready; // fed by CCD driver etc. and these can feed one big FSM -- or three different ones (all "driven" by the same FSM interpreter) Of course, you can also have: event_t UserInterfaceBusy; // signalled by UI FSM through which one FSM (ie., the one running the UserInterface) can pass events to another FSM! (Similarly, you can also use an event_t to allow a transition/action routine to pass "information" back to the FSM that invoked it! This allows you to keep decision making in the "state tables" while moving the mechanisms for detecting criteria into the action routines -- e.g.: event_t feedback = numeric_value_entered_was_zero; The advantage of this sort of approach is that the action routines are simple functions. They dont have to worry about changing states, etc. The "machine" is very obvious in the representation of the transition tables!
>>> 5. overly complicated in the commanding interface; the software tries to >>> handle a command queue with no control over the queue itself (queue >>> reset, queue status).
> What was specified in the specs was that for every command there should > be a reply, but unfortunately here also it was not clear when the reply > will come, since some processes are slower than others and so on. Given > the fact that the software had a "command queue" it would have been > possible to ignore the reply to the first command continuing sending the > others. > > Along the software implementation commands were added as the developer > needed one command more to accomplish what he wanted to do. The result > is a bunch of commands, each of them with its own interface of > parameters and no clear indication how each of them is processed.
OK. But that should be something you can grep from the sources. I tend to like skinny interfaces. Give me a few *flexible* commands that I can use to convey what I want -- even if it requires the issuing of several commands to get things done. This makes it easier to test he interface completely.
>>> 6. lacking control over the CCD; there's a register implemented in the >>> hardware which gives control over the CCD functionalities, but has been >>> ignored in the current implementation.
> My bad, I should have elaborated over this a little more. The CCD is > actually only the sensor, while an FPGA controls the way the chip shoots > the picture sample the data and then an ADC converts it to a digital > picture. This FPGA is controlled via a register that is write/read > accessible from the DSP.
So, the FPGA is a sequencer, of sorts, that "drives" the CCD. And the register governs the operation/options for the FPgA. (?)
>>> 8. not utilizing the available bandwidth; there's a serial port through >>> which the DSP can write its data to an output buffer, but the bandwidth >>> available is reduced to essentially 256/1920 due to the handshake >>> protocol implemented (where on the other side no one is really >>> performing any hand shake). >> >> Huh? I assume you mean the "other party" has no *need* for the >> handshaking (so it is wasted overhead)? Is the "handshaking" >> intended as a pacing mechanism or as an acknowledgement/verification >> mechanism? > > I believe it was intended as a pacing mechanism, since nobody is > verifying anything on the "other party". But the format of the message > didn't allow more than 256 bytes, effectively reducing the possibility > to send data out up to 1920 bytes/sec.
I don't understand where the 1920 limit comes from? That suggests a 19200 bad rate. Where does the 256 byte limit come into play?
>>> This limit poses a very hard constraint on >>> the science data, to the point where the information is not enough to >>> reconstruct pointing direction with the accuracy needed. >> >> Meaning you can't get data often enough to point the satellite >> (or your instrument therein) *at* the correct target? > > We reconstruct pointing on ground, i.e. every picture comes with a > timestamp and a the N brightest stars in the field of view. The bigger > is N the better the algorithm on ground recognize the stellar field. > Moreover the higher the frequency of sampling the higher the accuracy > (less need for interpolation). But all these factors are increasing the > volume of data that needs to be transferred.
<frown> I'm not sure I understand your explanation. But, it seems to still boil down to bandwidth?
>> 10. not designed for testing; there are a lot of functions that are not >>> observable and there's no logging mechanism in the code for tracing >>> either. It's what I usually call "plug and pray" system. >>> >>> I think there are other items that would call for a redesign, as lack of >>> documentation, lack of revision control system, lack of test campaigns, >>> lack of tools to work with the software, ... >> >> All these are "strongly desired" -- especially with the stakes as they >> are (I suspect it is very difficult/costly to get instruments flying!) > > Regarding testing, I had in mind to add a tracing mechanism (a sort of > printf) that would fill a log with some useful information that can be > dumped regularly or on request. The implementation shouldn't add too > much overhead but I believe that if used with care can give great > insights to the flow. As an example it could be possible to log how much > time it is spent in each function.
Yes. This is how my black boxes work. Hint: either provide a streamlined "bbprintf()" (Black Box printf) *or* discipline yourselves to only use inexpensive conversion operators. E.g., use octal or hex instead of decimal. Likewise, dump floats as hex values. You can write a post processor to examine those values at your leisure. The logging can then incur very little RUN-TIME processing overhead. I like to instrument my tasks with a stderr wired to a communication channel in my development library. So, each task can spit out progress messages that are automatically prefaced by the name of the emitting task: motorstart: building acceleration profile uarthandler: initializing UART uarthandler: allocating input FIFO motorstart: initializing runtime statistics uarthandler: allocating output FIFO motorstart: ready to command During development, I route these messages to my console so I can see where my code "is" without having to break and inspect it. This is especially useful as I can tell when all the initialization cruft is out of the way and the code should be "active".
> The shuttle flight to launch our experiment costs 500M$. The cost of the > experiment is evaluated 2B$.
Yikes!
>> In addition to serving as a map that "implementors" (coders?) can >> "just follow", a good spec gives you a contract that you can >> test against. And, for teams large enough to support a division >> of functions, lets the "test team" start designing test platforms >> in which the *desired* functionality can be verified as well as >> *stressed* in ways that might not have been imagined. This can >> speed up deployment (if all goes well) and/or bring problems in >> the design (or staff!) out into the open, early enough that >> you can address them ("Sheesh! Bob writes crappy code! Maybe >> we should think of having Tom take over some of his duties?" >> or "Ooops! This compression algorithm takes far too long to >> execute. Perhaps we should aim for lower compression rates >> in favor of speedier results?") > > This is why we actually prefer to have the an iterative and incremental > approach, the early testing would make us go back to redefine better the > specs and adjust the aiming along the way. > A waterfall model may result in problems if the specs are not so > thoroughly checked and at the same time they are engraved in stone.
Bad specs are worse than no specs. Why invest the time in writing them if you aren't going to write *good*/effective ones? It's like folk who purchase "backup devices" (tape, disk, etc.) but never USE them (cause they are too slow, tedious, etc.)
>>> Actually I tried to get the chance to have some good reference to foster >> >> That can be a good approach -- depending on the dynamics of >> your particular team. What you want to avoid is the distraction >> of people focussing on "arguing with the examples/guidelines" >> instead of learning from them and modifying them to fit *your* >> needs. >> >> [think about how "coding guidelines" end up diverting energy >> into arguing about silly details -- instead of recognizing the >> *need* for SOME SORT OF 'standard'] > > Gee that's another thing knocked me off. I don't blame people who have a > different coding style, as long as they have one. Lower and upper case > are one of people favorite leisure. It seems to me they let FATE decide > what would be the case of next letter in the word.... arghhh!
Sit them around a table and tell them they are responsible for debugging the code written by the person to their LEFT! :> Suddenly they have an interest in how *that* person writes their code! <shrug> I never fret "spacing" issues -- indentation, where braces are placed, etc. -- as those can be handled by a pretty printer. But, things like how identifiers are chosen should be done by some sort of concensus. SpeedMax vs MaxSpeed vs max_speed vs. ...
>> I like to approach designs by enumerating the "things it must do" >> from a functional perspective. I.e., "activities": control the >> transducer, capture data from the transducer, process that data, >> transmit that data... (an oversimplification of your device -- but >> I don't know enough about it to comment in detail). Note these >> are all verbs -- active. > > Personally I believe that block diagrams fulfill the need pretty well, > if some timing is needed then a waveform like drawing with a > cause-effect relationship between signals may help a lot to understand > the flow. > > What I've always seriously doubted is a flow chart of the program. They > rarely match what the program is doing (also because it would be nice to > see how to include your interrupts in a flow char) and often give the > impression that once you have it done the software is "automatically > generated". I personally have never seen a flow chart which corresponds > 1:1 to the program, maybe is just my lack of experience.
What is usually most important is how data flows through a program. Communication often defines performance. Needless copies, extra synchronization, etc. can quickly exceed the processing costs. I let the data paths define the structure of my tasks, etc.
>> This gives my first rough partitioning of tasks/threads/processes >> and "memory regions". It also shows where the data is flowing and >> any other communication paths (even those that are *implicit*). >> Synchronization needs then become obvious. And, performance >> bottlenecks can be evaluated with an eye towards how the >> design can be changed to improve that. > > I think synchronization is really complex whenever you are down to the > multi-thread business and/or have multiple interrupt servicing. Given > the old technology and luckily very few support for an OS (I haven't > found any), I was aiming to have a very simple, procedural design which > I believe would be much easier to test and to make it meet the specs.
You can implement VERY lightweight executives that still give you a basic framework to drape your code over. The problem with trying to do things as one procedure is that you, then, have to make and carry around mechanisms to give you any concurrency that you might need. That's why things end up migrating into ISRs -- because it represents an existing mechanism that can be exploited "alongside" your "regular code". Depending on how "rich" your environment is, you can probably implement a non-preemptive multitasking framework with very little effort. This also has the advantage of allowing you to carefully sidestep a lot of synchronization issues: you have control over all of your critical regions! If you don't want to deal with the possibility of another task "interrupting" what should be an atomic operation then don't *let* it! Coding in a HLL complicates this from the resource perspective. Mainly because of the pushdown stack and knowing how much is committed at any given point in time (where a reschedule() might happen).
> To backup a bit more this motivation I just finished to write an > extremely simple program to toggle a flag through the timer counter > interrupt. The end result is that I failed to get the period I want and > moreover is clear that interrupts are lost from time to time. > > Since in this last case I was kicking the dog with this flag, I actually > couldn't care less if I lost an interrupt as long as the period is > enough to keep the dog quiet. But I got discouraged by a post on > comp.dsp which stated: "This is embedded ABC basics: don't kick a dog in > the interrupts." but no motivation was given.
This is because the code can crash -- yet the ISR will (usually) still run! So, your application has died (needing the watchdog reset) but your ISR is happily stroking it to keep it from doing its job. I.e., code is stuck in an idiot loop executing NOOPs... ISR comes along, watchdog is stroked... code resumes its idiot loop...
> Now my point is, how much time should I invest to make it working rather > than exploiting a totally different path? If I had an infinite time I > would probably try to make this stupid interrupt work the way I expect > but these details may delay a lot if not irreversibly the project.
Whether or not you *fix* it, I think it is important that you identify the issue(s) that are preventing it from operating as intended. Those issues can reappear elsewhere, later, and cause you much pain.
>> E.g., if an (earth-based) command station has to *review*/analyze >> data from the device before it can reposition/target it, then >> the time from collection thru processing and transmission >> is a critical path limiting how quickly the *overall* system >> (satellite plus ground control) can react. If the times >> associated with any of those tasks are long-ish, you can >> rethink those aspects of the design with an eye towards >> short-cutting them. So, perhaps providing a mechanism >> to transmit *unprocessed* data (if the processing activity >> was the bottleneck) or collect over an abbreviated time window >> (if the collection activity was the bottleneck). > > This reinforce my personal opinion that having the design in a block > diagram form would allow for these kind of "shifts" easier to see. > Adding processing time to functions may give a lot more details to avoid > or bypass bottlenecks.
If you have data dependencies also obvious, it can give you an idea of where you can speed things up. E.g., if you are pulling data off of the CCD at <some_rate>, you might be able to simultaneously process some previously extracted data and cut down the total time required (by utilizing "waiting time" as "processing time")
>> Once the activities and communications are identified, I >> look to see what services I want from an OS -- and the >> resources available for it. IMO, choice of services goes >> a *LONG* way to imposing structure on an application! >> And, it acts as an implicit "language" by which the >> application can communicate with other developers about >> what its doing at any particular place. >> >> E.g., do you need fat pipes for your communications? >> Are you better off passing pointers to memory regions >> to reduce bcopy()'s? Can you tolerate shared instances >> of data? Or do you *need* private instances? How finely >> must you resolve time? What are the longest intervals >> that you need be concerned with? > > Here I'm a strong supporter of statically allocated memory, unless is > not enough. Dynamically allocated memory is one of the things most > programmers end up failing and even without noticing it. Countless the > amount of time I saw a malloc in the middle of the function which was > not free'ed at the end. > > Shared memory is also something that unless needed should be avoided > IMO. What is the advantage to have a memory shared when there's enough > available?
You share when there *isn't* enough available! :> I've worked on resource starved designs where individual *bits* were reused based on what the device was doing at the time. (it is not fun)
>> I would also set aside some resources for (one or more) >> "black boxes". These can be invaluable in post-mortems. > > What do you mean post-mortem?
In the event of a crash (or something "unintended"). E.g. if the watchdog ever kicks in, you can examine the contents of those logs *before* overwriting them (like a crash dump) to determine why the watchdog wasnt stroked properly.
>> Ideally, if you have a ground-based prototype that you >> can modify, consider adding additional memory to it >> (even if it is "secondary" memory) for these black boxes. >> Having *lots* of data can really help identify what is >> happening when things turn ugly. (much easier than >> trying to reproduce a particular experiment -- which >> might not be possible! -- so you can reinstrument it!) >> Litter your code with invariant assertions so you see >> every "can't happen" when it *does* happen! :> > > I agree and this is why I believe I would need to add a logging > capability, in order to see what happened after the fact. > I never thought about changing the hardware, since I've always believed > that adding hardware is preventing me from building the tools and maybe > the software structure. A good example is the emulator. I see people are > increasingly developing with the emulator, then they have to unplug it > and give the product away and now they don't have any tool to assess the > state/functionality since they were always used to use the emulator and > now they cannot work anymore without it.
Emulators are cheap (relatively speaking). Of course, you might not be able to FIND an emulator for a particular processor! I started my career burning 1702's, plugging them into a prototype and *hoping*. Even with multiple sets of "blank spares", this limited me to ~4 turns of the crank in an typical day. Emulation, symbolic debugging, profiling, etc. are well worth the equipment costs! Even without an emulator, you can often make small changes to the hardware to greatly increase your productivity. Being able to quickly update "ROM images" saves lots of time. Whether it is FLASH or static/BB RAM... as long as it can be erased/overwrtten quickly and reliably in situ (add a write protect switch). You can also write a simple debugger to reside in the image. THis can allow you to examine and modify the state of the processor and its processes. If you have a multitasking environment, that debugger can allow you to debug a process while others continue executing normally. (if processes have interlocking semaphores/etc., then stalling the one process will cause the dependent processes to stall accordingly) Even "crude" tools like this can give you significant advantages. E.g., look into supporting the remote stub for gdb if that fits your envirnoment.
On 2011-12-16, Don Y <not.to.be@seen.com> wrote:
> Hi Simon, > > On 12/16/2011 10:51 AM, Simon Clubley wrote: >> I find that the number of unsigned integers in my code is _vastly_ >> greater overall than the number of signed integers. >> >> Personally, I think C should have made unsigned integers the default. > > I think C should have required the signed/unsigned syntax. > Why are ints different than chars (also an integer data type)? >
They aren't in my view (at least in C). As chars are assumed, like ints, to be signed unless declared otherwise my mental model of a C bare char declaration is a 8 bit signed integer so my comments also apply to char declarations. BTW, I like the idea of requiring signed/unsigned. It would reduce the chance of a lazy programmer just declaring everything as implicitly signed because they could not be bothered to type "unsigned". :-) OTOH, I'm the kind of person who likes Ada's strict type system so I accept I'm probably not the typical C programmer. :-)
>> BTW, on a related data representation note, another thing I like to do is >> when I build something like a state machine is to start the numbers >> assigned to the state symbols at value 1, instead of value 0 so that I >> stand a greater chance of catching uninitialised state variables. > > I tend to build state machines by tabulating the state tables: > > state_t states[] = { > ... > &some_state > &someother_state > &yet_another_state > ... > } > > #define NUMBER_OF_STATES (sizeof (states)/sizeof (state_t)) > > Then, explicitly setting the initial state to correspond with ><whatever_state>. state_t is then more of an enumeration. > I do this because I support "special next_state encodings" > like SAME_STATE, "POP_STATE", etc.
Interesting. The state machine was just one example. What I was trying to say is that in anything in which I need to maintain state in C, whether it be a main loop state machine or a tree like data structure with nodes in various states of processing, the variable I use to maintain state information is a enum with it's first valid state symbol set to one. The idea is that since the state variable is likely to start out as zero (.bss or zeroed allocated memory), I'm more likely to catch coding errors (I don't assume I'm a perfect programmer :-)), rather than just assume some node has been correctly setup in it's initial state. Simon. -- Simon Clubley, clubley@remove_me.eisner.decus.org-Earth.UFP Microsoft: Bringing you 1980s technology to a 21st century world
On 12/16/2011 10:26 PM, Simon Clubley wrote:

>>> I find that the number of unsigned integers in my code is _vastly_ >>> greater overall than the number of signed integers. >>> >>> Personally, I think C should have made unsigned integers the default. >> >> I think C should have required the signed/unsigned syntax. >> Why are ints different than chars (also an integer data type)? > > They aren't in my view (at least in C). As chars are assumed, like ints, > to be signed unless declared otherwise my mental model of a C bare char > declaration is a 8 bit signed integer so my comments also apply to char > declarations. >
The signedness of bare chars is implemented defined, presumably to allow the compiler to pick whatever is easier to implement on the target.
Hi Simon,

>> I think C should have required the signed/unsigned syntax. >> Why are ints different than chars (also an integer data type)? > > They aren't in my view (at least in C). As chars are assumed, like ints, > to be signed unless declared otherwise my mental model of a C bare char > declaration is a 8 bit signed integer so my comments also apply to char > declarations.
Ah, but the signedness of chars is defined by implementation -- hence my comment. In other words, if you are going to let the default vary for chars -- thereby requiring me to explicitly indicate a char's signedness -- then why doesn't it, also, vary for *real* ints??
> BTW, I like the idea of requiring signed/unsigned. It would reduce the > chance of a lazy programmer just declaring everything as implicitly > signed because they could not be bothered to type "unsigned". :-)
IMO, if I discipline myself to type "(un)signed", then I have to *think* about that for at least a moment: "What do I really want this to be?". Ditto short/long/long long/etc.
> OTOH, I'm the kind of person who likes Ada's strict type system so I > accept I'm probably not the typical C programmer. :-) > >>> BTW, on a related data representation note, another thing I like to do is >>> when I build something like a state machine is to start the numbers >>> assigned to the state symbols at value 1, instead of value 0 so that I >>> stand a greater chance of catching uninitialised state variables. >> >> I tend to build state machines by tabulating the state tables: >> >> state_t states[] = { >> ... >> &some_state >> &someother_state >> &yet_another_state >> ... >> } >> >> #define NUMBER_OF_STATES (sizeof (states)/sizeof (state_t)) >> >> Then, explicitly setting the initial state to correspond with >> <whatever_state>. state_t is then more of an enumeration. >> I do this because I support "special next_state encodings" >> like SAME_STATE, "POP_STATE", etc. > > Interesting. > > The state machine was just one example. What I was trying to say is > that in anything in which I need to maintain state in C, whether it > be a main loop state machine or a tree like data structure with nodes > in various states of processing, the variable I use to maintain state > information is a enum with it's first valid state symbol set to one.
Understood.
> The idea is that since the state variable is likely to start out > as zero (.bss or zeroed allocated memory), I'm more likely to catch
Note that some implementations fail to clear that memory on startup! I.e., "0x27" is just as likely to be an uninitialized value.
> coding errors (I don't assume I'm a perfect programmer :-)), rather > than just assume some node has been correctly setup in it's initial > state.
I like to initialize values when they are declared. Sometimes, even deliberately picking a value that I *know* is incorrect (0 in your example).
On 2011-12-16, Arlet Ottens <usenet+5@c-scape.nl> wrote:
> On 12/16/2011 10:26 PM, Simon Clubley wrote: > >>>> I find that the number of unsigned integers in my code is _vastly_ >>>> greater overall than the number of signed integers. >>>> >>>> Personally, I think C should have made unsigned integers the default. >>> >>> I think C should have required the signed/unsigned syntax. >>> Why are ints different than chars (also an integer data type)? >> >> They aren't in my view (at least in C). As chars are assumed, like ints, >> to be signed unless declared otherwise my mental model of a C bare char >> declaration is a 8 bit signed integer so my comments also apply to char >> declarations. >> > > The signedness of bare chars is implemented defined, presumably to allow > the compiler to pick whatever is easier to implement on the target. >
Interesting. That's something I'd forgotten about as I always declare them in full. Many years ago, before I started doing that, it seemed that every compiler I used treated a bare char declaration as signed. Is there literature somewhere online which compares what the current major compilers do on various architectures/operating systems about implementation defined issues like this ? I had a look before posting, but I couldn't find anything. BTW, that's a major argument for the position that the programmer should be required to explicitly declare the variable as signed or unsigned. Simon. -- Simon Clubley, clubley@remove_me.eisner.decus.org-Earth.UFP Microsoft: Bringing you 1980s technology to a 21st century world
Hi Simon,

On 12/16/2011 3:24 PM, Simon Clubley wrote:
>> The signedness of bare chars is implemented defined, presumably to allow >> the compiler to pick whatever is easier to implement on the target. > > Interesting. > > That's something I'd forgotten about as I always declare them in full. > Many years ago, before I started doing that, it seemed that every compiler > I used treated a bare char declaration as signed. > > Is there literature somewhere online which compares what the current major > compilers do on various architectures/operating systems about implementation > defined issues like this ? I had a look before posting, but I couldn't find > anything. > > BTW, that's a major argument for the position that the programmer should > be required to explicitly declare the variable as signed or unsigned.
For most folks, signedness of chars isn't an issue -- *if* you are using them as "characters". E.g., 7b ASCII doesnt care. Even 8b encodings still let you do many simple arithmetic operations on chars without concern for sign. For example, '2'++ is '3', regardless of encoding. However, ordering arbitrary characters an vary based on implementation. Where signedness is a real issue is when you try to use chars as "really small ints" -- short shorts! There, it is best to define a type to make this usage more visible (e.g., "small_counter") and, in that typedef, you can make the signedness explicit.
On 2011-12-16, Don Y <not.to.be@seen.com> wrote:
> > Ah, but the signedness of chars is defined by implementation -- hence > my comment. In other words, if you are going to let the default vary > for chars -- thereby requiring me to explicitly indicate a char's > signedness -- then why doesn't it, also, vary for *real* ints?? >
And, since I declare all variables in full, I had forgotten about that little implementation defined feature as it's something I don't have to tackle these days. :-)
>> >> The state machine was just one example. What I was trying to say is >> that in anything in which I need to maintain state in C, whether it >> be a main loop state machine or a tree like data structure with nodes >> in various states of processing, the variable I use to maintain state >> information is a enum with it's first valid state symbol set to one. > > Understood. > >> The idea is that since the state variable is likely to start out >> as zero (.bss or zeroed allocated memory), I'm more likely to catch > > Note that some implementations fail to clear that memory on startup! > I.e., "0x27" is just as likely to be an uninitialized value. >
Yes, I'm aware it's a possibility. On bare board embedded code, I now write my own startup code (I don't use vendor code any more) which does do this. In normal operating systems, I make sure any dynamically allocated memory is zeroed in my code. Simon. -- Simon Clubley, clubley@remove_me.eisner.decus.org-Earth.UFP Microsoft: Bringing you 1980s technology to a 21st century world
On 16.12.2011 22:57, Don Y wrote:

> Note that some implementations fail to clear that memory on startup!
Such implementations would have to be classified as spectacularly broken. You would need a pretty strong excuse for using any such toolchain despite such failures.
> I.e., "0x27" is just as likely to be an uninitialized value.
No. It's possible, but nowhere near as likely. You can make some implementations not intialize the .bss region (or meddle with the startup to that end), but the ones where that happens by accident are certainly _far_ outnumbered by the standard-conforming ones.

Memfault Beyond the Launch