Advertisement

Preemptive real-time software debugging

Real-time code is hard enough to write without fighting the compiler

BY JOE DRZEWIECKI
Senior Manager of Compilers
& Software Development
Microchip Technology
www.microchip.com

Software engineers creating embedded software, and especially real-time embedded software, are always looking for imaginative ways to increase the effectiveness and efficiency of their code. This article presents some often-overlooked ways to use your compiler to prevent debugging in real-time systems.

Errors and warnings

In the same vein as “only floss the teeth you want to keep,” you should only clear compiler errors and warnings from those files that you want to work properly. Many engineers can’t help but think that compiler errors and warnings are criticisms of their coding. In truth, of course, every message represents the compiler telling you that it’s either unsure of how to interpret what you’ve written, or worse, that it’s pretty sure that what you wrote is not what you meant. It’s much easier to ensure that the compiler is able to properly interpret your code, including, heaven forbid, actual coding errors, than it is to determine coding errors from the real-time performance debug of your product code.

Preemptive real-time software debugging

Fig. 1: Microchip’s MPLAB XC compilers provide many helpful error and warning messages.

Although cleaning up compiler errors and warnings is tedious, finding misbehaving real-time code on your board is ten times worse! If you write tons of code and try to rush a clean compile, you’re asking for a boatload of frustration. Real-time code is hard enough to write without fighting the compiler. Write small chunks of code and compile them frequently to make sure the compiler understands them. Also, start at the very beginning and clean up only the first error or two, and then re-compile. Even a single missing semicolon can generate so many error messages that the compiler stops printing them.

Preemptive real-time software debugging

Fig. 2: Pay attention to this warning message from your MPLAB XC32 compiler.

Designers know what they should do when coding — short functions with a single purpose, clean control flow, good encapsulation of data (limit global variables), and coherent pointer/array usage. It makes the task of completing your real-time assignments that much more efficient. If you have a tough time being disciplined in what you know would make your code work better, you should seriously consider using a MISRA (Motor Industry Software Reliability Association) checker. MISRA has a number of rules that limit the most often abused features of the C programming language. If you stick with MISRA, you’ll get more deterministic execution. You may even be surprised to see your code size shrink after you’ve coded things so cleanly.

Optimization

More than any other aspect of real-time and embedded programming, optimization causes the most nervousness. Here are a few tips about optimization.

1. Choose an algorithm carefully.

Picking a good, or great, algorithm will beat an optimizing compiler every time. The compiler’s most important job is to generate “correct” code, not efficient code. Many programmers mistakenly assume that an optimizer is there to change their code, which it won’t.

2. Avoid inline assembly.

Engineers with real-time constraints often assume they have to use inline assembly. The old adage says “make it work, then make it fast.” These engineers should consider writing completely in C first. However, don’t avoid inline assembly because it is inherently bad, avoid it because it is difficult to write correctly and usually unnecessary. Try using built-in functions of the compiler. If you absolutely must use inline assembly, ensure that you understand and use the “extended” form, so that you can make your usage clear to the compiler, and use C variables, not the underlying registers.

3. Remove code that has no effect — the bane of real-time functionality.

Optimizers won’t change programmers’ code. So, how do they reduce code size? One of several answers lies in the former mystery of code that has “no effect.” When the compiler reduces the size of your code, it can’t arbitrarily throw things away, so one of the tricks it uses is to look for code that has “no effect.” It’s important that you understand what a compiler means by this. Take the following timing loop, for example:

for(i = 0; i

// wait 1 us

You know that a microsecond is important, but the compiler doesn’t have any conception of the passage of time, so this entire construct will be eliminated by the optimizer because this code has “no effect.”

The general principal by which optimizations remove expressions that have “no effect” is as follows: if the compiler can deduce that values in the expression are not used and that no needed side effects are produced, it is free to remove the code. There are several ways around this, one of which, volatile , is covered in the next section.

4. Manage shared variables, the volatile modifier, and uninterrupted access.

Concurrent operations, such as interrupt handlers, are a key part of real-time programming. It is often necessary to share variables with ISRs. Shared variables are frequently a source of problems under optimization because the compiler, unless told otherwise, assumes that it can see everything that happens to a variable. The modifier volatile is often used to protect shared variables between concurrent execution events. In essence, volatile tells the compiler that it doesn’t know everything about a variable, with the side effect of preventing it from optimizing access to that variable.

Volatile doesn’t imply that an access is uninterrupted (atomic, done in one step). Even a simple assignment in mainline code like:

volatile long x = 3;

(that looks like it is atomic) could actually be jeopardized by a similar assignment in an ISR. An 8-bit compiler will probably compose a 32-bit “long” write out of four consecutive 8-bit writes. Any of those writes may be interrupted at any time. If the assignment to x is interrupted and the ISR writes to x, when control returns to the mainline, x will likely be corrupted.

A good way to control the access of a shared variable is through the use of accessor functions. Accessor functions provide common code where the programmer can decide the level of safety required for each variable and ensure that each access follows the rules. In the previous case, one might decide to disable interrupts when writing to x. If you need to guarantee atomicity, try accessor functions instead of using volatile .

5. Watch unfettered casting of pointers (also known as coercion or type punning)

Casting is a means for circumventing the type system using pointers, and is another clever but misguided way to completely mess up the operation of real-time code. According to standard syntax, casting pointers is syntactically allowed, but it is often semantically invalid (that is, it doesn’t work). At first glance, it may seem efficient (and may even work on a particular architecture at a given optimization level), to get to the exponent of an IEEE 754 floating-point number with the following:

float x = 186000;

int exponent = ((*(int*)(&x)) & 0x7F800000) >> 23;

However, this kind of expression is fragile and can yield unexpected results with different compilers or optimization settings. Instead of casting pointers, use a union, such as:

union {

float f;

struct float_format s;

} v;

Once the value is assigned to v.f, the exponent, or any other field, can be accessed easily through the structure. Under optimization, the compiler will be able to make the access as safe as it is efficient.

Modern compilers

Modern compilers, such as Microchip’s MPLAB XC line, are powerful tools capable of helping real-time systems programmers accomplish a great deal in a short amount of time. Like any other tool, they must be used skillfully and you must “listen” to the tool. The compiler makes that as easy as reading. With proper understanding a lot of the frustration and mystery associated with compilers and optimization can be avoided. ■

Advertisement



Learn more about Microchip Technology

Leave a Reply