Saturday, March 23, 2013

Customizable Makefile for GCC ARM Embedded Toolchain

______________________________________________

Step 1: Setting Up Toolchain Folders
Step 2: Options for Cortex Processors
Step 3: Makefile Project Settings
Step 4: Makefile General Settings
Step 5: Makefile Command-Line Generation
Step 6: Makefile Execution
Complete Makefile
______________________________________________


A few weeks ago I successfully installed GNU GCC ARM Embedded Toolchain and configured my Eclipse Indigo IDE to develop firmware for Cortex embedded processors.

Here you are a very interesting and helpfull link (http://www.emb4fun.de/arm/eclipse/index.html) where it is explained, step-by-step, how to setup your Eclipse IDE + CDT + GCC ARM Toolchain to support Cortex embedded development.

I have not installed Yagarto, but a different ARM Embedded Toolchain, and have tested it by debugging a mbed LPC1768 board (cortex-m3) by following the instructions in the link above (also using a Segger J-Link EDU debugger), and I must say that it works really, really fine.

Well, let's go into the matter.

In order to make binaries for any embedded Cortex (M/R) processor, I've gone through arm-none-eabi toolchain documentation and adapted a makefile to make me easier the task of building code for different target processors with minimum changes in that makefile.

In the following sections I explain how does the makefile works...really simple, for sure!


Step 1: Setting Up Toolchain Folders

In this first section we are declaring some variables to keep track of the folders containing toolchain binaries, libraries and header files.

We are also building the paths for each binary into makefile variables... AS, CC, CCP, LD and OBJCOPY.

Just change it to fit your toolchain installation.




Step 2: Options for Cortex Processors

In this section we declare a couple of makefile variables for each target processor (M0, M0+, M1, M3, M4, R4, R5).

  • CORTEX_xxxx_CC_FLAGS stores mcu specific GCC command-line options
  • CORTEX_xxxx_LIB_PATH stores mcu specific prebuilt libraries supplied with the toolchain. Check your toolchain path to find where these libraries are stored.
If you are using the same toolchain as me, you should not need to change a line in this section.

Please note that NOFP, SWFP and HWFP modifiers in cortex-m4, cortex-r4 and cortex-r5 targets, stand respectivelly for no-floating-point, software-floating-point and harware-floating-point implementations.





Step 3: Makefile Project Settings

Here you must enter specific options for the project you want to build. Just a few comments about this section:
  • MCU_CC_FLAGS and MCU_LIB_PATH receive the variables CORTEX_xxxx_CC_FLAGS and CORTEX_xxxx_LIB_PATH related to your target processor.
  • OPTIMIZE_FOR variable must be set to SIZE to optimize code for size ( OPTIMIZE_FOR = SIZE ), or leave it undeclared ( OPTIMIZE_FOR =  ) otherwise.
  • DEBUG_LEVEL defines the option to append to -g compiler modifier. For example, if you set DEBUG_LEVEL = 3, the command line will include -g3 option. If you leave this variable undeclared, no -g option will be included into gcc command line.
  • OPTIM_LEVEL works in the same way as DEBUG_LEVEL, but relates to -O optimization modifier. For example, if you set OPTIM_LEVEL = 1, the command line will include -O1 option. If you leave this variable undeclared, no -O option will be included into gcc command line.

Other variables in this section allows you to include custom object files, paths to project libs and headers, preprocessor symbol definitions, etc.

Customize it as your needs...




Step 4: Makefile General Settings

In Makefile General Settings section we are declaring variables to include paths to toolchain libraries and headers folders, and some library inclussion based on the optimization options declared in the previous section, by using conditional ifeq_else_endif makefile instructions.

These variables reference toolchain-related elements (objs, libs, headers, paths, etc), not project-related ones.

Later in command-line building section we are merging toolchain-related and project-related options into a single command-line for each toolchain binary as required.




Step 5: Makefile Command-Line Generation

As explained above, in this section we are building the whole command lines by merging project and toolchain specific compiler options and preprocessor symbols.

In a first approach you should not need to chage a line in this section because it processes variables already declared in previous sections..., but of course you are free to change it if your build requires it...




Step 6: Makefile Execution

Last section has no special nor additional comments about it. It just cleans generated files from previous builts and performs the execution of toolchain binaries to build your project.




Complete Makefile 

And finally, the complete makefile is shown below...



Tuesday, March 5, 2013

Hard Fault Debugging for Cortex-M MCUs

______________________________________________

1. Why HARD_FAULT exception raises?
    1.1. Uncontrolled/unexpected memory accesses.
    1.2. Accesses to unclocked peripherals.
    1.3. Executing Flash commands from code in Flash
2. Finding HARD_FAULT exception.
    2.1 General notes about the code
    2.2 Take special care with....
    2.3 The code
______________________________________________


In this post we are explaining how to debug HARD_FAULT exceptions on Cortex-M processors as well as locating the origin of the exception condition.

There are lots of sites describing this problem, and most of them show exactly the same code.

Of course, the code did not properly compiled for my target processor (Kinetis / Cortex-M4), so I made some small changes in order to get it working.

Finally I have modified the code to compile it using GNU GCC ARM Toolchain for many Cortex-M targets and thus, the code should properly compile using any GCC compatible toolchain (or it is supposed to do it properly).

Before going into the code, let's just say a few words about HARD_FAULT exception causes


1. Why HARD_FAULT exception raises?

Most commom causes raising HARD_FAULT exceptions are the following::

1.1. Uncontrolled/unexpected memory accesses.

This happens due to programming errors that result in uncontrolled accesses to restricted or forbidden memory locations.

On the best situation, HARD_FAULT exception is due to stupid errors which are not noticed by compilation process: like miss-typing the name of variables with simmilar names (i.e. exchanging 'u8aux' and 'u8idx' when both variables are declared in the same context), by array indexes going out of bounds, and things like those....

On the worst case, the exception may be raised due to some issues harder to debug, like wrong return from function or ISR due to stack corruption, etc.

1.2. Accesses to unclocked peripherals.

Some MCUs control clock distribution to each system module or peripheral to reduce power consumption to the bare modules in execution.

In these kind of MCU's you must enable clocking into the peripheral module prior to accessing any peripheral register or be pretty sure that HARD_FAULT exception will raise.

1.3. Executing Flash commands from code in Flash

When the MCU does not support "read while write" flash feature, HARD_FAULT exception may rise when executing flash commands (like sector erasing or writing) from code stored in flash and apparently not affected by the flash operation.

The easiest way to solve this problem is running flash command code from code stored into RAM memory instead of flash. To d this, just set up your linker configuration file to load flash driver code into RAM on startup. (Visit this post to get an example of this issue).


2. Finding HARD_FAULT exception.

The main target of the code shown below is recovering the processor execution context that was present when the exception raised (program counter, stack pointers, register contents, etc.)

In order to understand the code below, we must consider that Cortex-M MCUs process HARD_FAULT exception like a higher priority interrupt, and so, they store the processor execution context into the stack before jumping to HARD_FAUL handler vector. This way, we can extract the execution context that raised the exception from the stack.

The code below declares a HARD_FAULT exception handler "hardfaultHandler" that determines which stack PSP or MSP was active when the exception happened and calls another function "hardfaultGetContext" that extracts the stacked context into local variables.

2.1 General notes about the code

The code shown below has been compiled using GNU GCC ARM Toolchain running from Eclipse IDE.
It successfully compiles without errors targeting the following Cortex-M processors:
  • Cortex-M0
  • Cortex-M0+
  • Cortex-M1
  • Cortex-M3
  • Cortex-M4 (without floating point processor)
  • Cortex-M4 (with software floating point)
  • Cortex-M4 (with hardware floating point processor)

2.2 Take special care with....

There is a special issue included in the code below that I have not found in other blogs nor websites and that took me a couple of painfull days to go through it.

Most hardfault code published in Internet calls hardfaultGetContext function from inline assembler (using unconditional branch to label BL instruction). The following code shows an example of wrong code found on Internet...



By default, the code above issues an assembler error because the assembler does not know the symbol or label  "hardfaultGetContext" nor "_hardfaultGetContext".

To avoid the error you must assign an assembler label to the entry point of "hardfaultGetContext" function and branch to that label from inline assembly. In the following code snippet you can see how to assign an assembler label to a C function...



2.3 The code

And finally here you are the complete code... enjoy it!