NASA engineer recounts Curiosity’s landing on Mars and more



[ad_1]

>> Electronic design resources
.. >> Library: series of articles
.. .. >> Topic: system design
.. .. .. >> Series: Improving the quality of the software code

It comes as no surprise to anyone that newly written software is rarely 100% bug-free. However, there are steps you can initially take to reduce the number of problems that can appear in your code. Or, to put it another way, to make sure you have less debugging to do. The clear starting point is to establish some basic code hygiene rules:

  • Use a coding standard such as MISRA and CERT C. Committing to compliance with MISRA helps you avoid some of the pitfalls inherent in C and C ++. CERT C can add a security perspective to the list of things to avoid. The first corollary is to pay close attention to the compiler warnings. The second corollary is to use an automated static check to verify compliance.
  • Use yours or someone elses, hardware abstraction layer. Avoid inlining code that directly manipulates hardware in code. For example, if you need to start a timer, call a HAL function to set and start the timer instead of directly manipulating the timer registers. You will realize numerous benefits by listening to this advice, one of which is to almost completely avoid copy / paste errors and typos when dealing with several timer invocations at different points in the code base. In addition, a compiler can often do a better job at optimizing and may even embed code, in order to achieve both performance and code size. A corollary to this advice is to keep the individual HAL functions as small as possible, avoiding creating “Swiss Army Knives” (large functions with many responsibilities). Not only are small, single-use functions easier to understand and maintain, but they are also often easier to optimize for a very good compiler, which can seem a little counterintuitive.
  • Think a little more about how you use your memory. For example, do you really need dynamic memory management? Is the stack really a good place to store complex data structures? Standards for functional safety and high-integrity software often strongly advise against dynamic memory management and storing complex or large data structures on the stack, and these are for good reason.
  • If your toolchain supports worst case stack depth analysis, the investment to research and use this functionality will pay off quickly.

Printf or Don’t Printf shouldn’t be the problem

One of the first things to realize (or remember) is that if you develop and debug embedded software, you are most likely doing it in an environment where code execution on the target is done through a debugger. For example, if you are working in an IDE, the easiest way to run your program is to start the debugger.

Let’s go to the hard core and look at the power of breakpoints. But first, leavecasts a little shadow on the venerable printf as a debugging tool.

The most important reason for not using printf is that addition printf-Declarations in the code can greatly affect how the code is compiled. Not just the printf a function call, but the arguments of the call will need to be considered. This means that stack and register usage will look completely different and many compiler optimizations will fail, especially if the statement is in a tight loop.

Such a scenario can have unpredictable consequences if your code is complex or based on implementation-defined or even non-standard C / C ++ C / C ++ behavior. What might happen is that your code behaves perfectly when you add the file printf to code, but it breaks when you remove the print or vice versa? By the way, this is a very good reason to strive for MISRA compliance.

Another good reason is that printf it is a weak tool as it can only display data. A third reason is that to change the printing behavior or add more printing instructions, the application must be rebuilt and downloaded to the destination again. Finally, at some point you will have to go through the code base and remove any instructions you added, even if they are all protected with #ifdefs.

The power of breakpoints

So, let’s take a break from preaching and look at the different types of breakpoints available. A breakpoint, in its simplest form, is a stop signal in a particular source instruction that ensures execution stops unconditionally when the right point is reached. A good debugger will allow you to examine the contents of variables, registers, and call stacks, as well as memory in general. Such a code breakpoint is very useful in its own right, but it can also be associated with an expression whose truth value determines whether or not execution aborts.

This allows you to focus on the interesting cases instead of examining the interesting variables each time the execution passes through the location of the breakpoint. For example, if you want to take a closer look at what’s going on in a specific range of values ​​in a loop index variable, you can set the expression to stop only when the index is at that. interval rather than stopping every time you press that code. Of course, you can also build more complex stop expressions based on any variable that is in scope.

Sometimes you really need to see the value of one or more expressions. This can easily be done by using a log breakpoint, a breakpoint whose sole purpose is to print a message in the debug log window without interrupting execution. It is essentially a provided debugger printf which can be combined with a Boolean expression to determine whether the message should be generated or not.

A very powerful type of breakpoint is the data breakpoint. This activates a specific variable or accesses a memory location. This can be extremely useful if you are trying to understand why the data values ​​in a specific location are not what you expect.

Why should it be necessary? There can be several reasons, but one of the sources of such problems is pointers. If you use pointers, there is a good chance that at some point you will get some pointer arithmetic wrong. While reading or writing to the wrong address may not cause the program to fail, it can produce very strange results. This type of problem can be very difficult to debug, as the actual bug and where it occurs are often not related in any way.

The combination of data breakpoints (or any kind of breakpoint, for that matter) with the call stack window can be very revealing (Fig. 1). The call stack window will show you where you are from. It also offers the opportunity to move up and down the call chain and examine parameter values.

1. Edit breakpoint.  Combining data breakpoints (or any kind of breakpoint, for that matter) with the call stack window can be very revealing.1. Edit breakpoint. Combining the data breakpoints (or any kind of breakpoint, for that matter) with the call stack window can be very revealing.

Some of these types of breakpoints may not always be available, depending on the exact device running the program and / or the specific debug probe.

Some targets support real-time memory reading, so the debugger can continuously display variable values ​​and other information while running with a standard debug probe.

A path to enlightenment

If you can handle a few more buzzwords and adjectives, let’s talk about a debugging tool that’s truly amazing. Tracing is a way to record execution and other types of data flow on the device, such as interrupt information and other hardware events (Fig.2). For example, viewing data from events combined in a timeline can reveal a lot about the behavior of a system: Do outages trigger when they should and how are they related to other activities?

2. Tracing is a way to record execution and other types of data flow on the device, such as interrupt information and other hardware events.2. Tracing is a way to record execution and other types of data flow on the device, such as interrupt information and other hardware events.

What makes tracing more complex than normal debugging is that there are many different types of trace technologies and different ways to access trace data. Additionally, a trace-enabled probe may be required. Using the power of the track in the best way means thinking about what you need to do to use it at the beginning of the project:

  • One thing to consider is the choice of device. Does it have trace functionality, and if so, what type? Is the device available in tracked and trackless versions? If so, you can create development versions of your board with trace and go to production without it to keep costs down.
  • Tracing can also be an enabler for profiling and code coverage data, so thinking ahead of your needs in that area can be helpful.

The high-quality tracing tools are designed to eliminate trace complexity and use all available trace information, but you still need to understand your hardware needs. However, investing a bit of time and resources upfront in tracing as a debugging and code quality tool will pay off when you encounter the first tricky problem.

The path to greater efficiency

Some of the topics in this article may seem trivial to the limit, but the best solutions to sensitive problems often fall into this category. Finding the root cause of a software problem can take days or even weeks, or it can be a quick and easy process. One way to reduce the difficulty is to spend a moment thinking about how to best use your knowledge of the code base in combination with the features of your debugger and tracing tools, rather than always looking for a printf statement. Over time, this way of working will increase productivity and efficiency, not to mention peace of mind.

Anders Holmberg is General Manager, Embedded Development Tools, at IAR Systems.

>> Electronic design resources
.. >> Library: series of articles
.. .. >> Topic: system design
.. .. .. >> Series: Improving the quality of the software code

[ad_2]
Source link