In the final third part in a series, Joseph Yiu, author of “The definitive guide to the ARM Cortex-M0,” takes you through means by which to port your code base from an 8/16-bit MCU to the ARM Cortex-M0.
Some application developers might need to port applications from 8-bit or 16-bit microcontrollers to the Cortex-M0. By moving from these architectures to the Cortex-M0, often you can get better code density, higher performance, and lower power consumption.
Common Modifications: When porting applications from these microcontrollers to the Cortex-M0, the modifications of the software typically involve the following:
1- Startup code and vector table. Different processor architectures have different startup code and interrupt vector tables. Usually the startup code and the vector table will have to be replaced.
2- Stack allocation adjustment. With the Cortex-M processors, the stack size requirement can be very different from an 8-bit or 16-bit architecture. In addition, the methods to define stack location and stack size are also different from 8-bit and 16-bit development tools.
3- Architecture-specific/tool-chain-specific C language extensions. Many of the C compilers for 8-bit and 16-bit microcontrollers support a number of C language extensions features. These include special data types like Special Function Registers (SFRs) and bit data in 8051, or various “#pragma” statements in various C compilers.
4- Interrupt control. In 8-bit and 16-bit microcontroller programming, the interrupt configuration is usually done by directly writing to various interrupt control registers.
When porting the applications to the ARM Cortex-M processor family, these codes should be converted to use the CMSIS interrupt control functions.
For example, the enable and disable functions of interrupts can be converted to ” enable irq () ” and ” disable irq () “. The configuration of individual interrupts can be handled by various NVIC functions in CMSIS.
5- Peripheral programming. In 8-bit and 16-bit microcontroller programming, the peripherals control is usually handled by programming to registers directly. When using ARM microcontrollers, many microcontroller vendors provide device driver libraries to make use of the microcontroller easier.
You can use these library functions to reduce software development time or write to the hardware registers directly if preferred. If you prefer to program the peripherals by accessing the registers directly, it is still beneficial to use the header files in the device driver library as these have all the peripheral registers defined and can save you time preparing and validating the code.
6- Assembly code and inline assembly. Obviously all the assembly and inline assembly code needs to be rewritten. In many cases, you can rewrite the required function in C when the application is ported to the Cortex-M0.
7- Unaligned data. Some 8-bit or l6-bit microcontrollers might support unaligned data. Because the Cortex-M0 does not support unaligned data, some data structures definitions or pointer manipulation codes might need to be changed.
For data structures that require unaligned data handling, we can use the _packed attribute when defining the structure. However, the Cortex-M0 requires multiple instructions to access unaligned data. So it is best to convert the data structures so that all elements inside are aligned.
8- Be aware of data size differences. The integers in most 8-bit and 16-bit processors are 16-bit, whereas in ARM architectures integers are 32-bit. This difference causes changes in behavior of overflow situations, it can also affect the memory size required for storing the data.
For example, when a program file defines an array of integers from 8-bit or 16-bit architecture, we might want to change the code to use “short int” or “intl6_t” (in “stdint.h,” introduced in C99) when porting the code to ARM architecture so that the size remains unchanged.
9- Floating point. Many 8-bit and 16-bit microcontrollers define “double” (double precision floating point) as 32-bit data. In ARM architecture, “double” is 64-bit.
When porting applications containing floating point operations, you might need to change the double precision floating point data to “float” (single precision floating point).
Otherwise the processing speed would be reduced and the program size could increase because of the requirement to process the data in extra precision.
For the same reason, some function calls for mathematical operation might need to be changed to ensure that the single precision version is used. For example, by default “cos () ” is the double precision version of the cosine function; for single precision operation, use “cosf()” instead.
10- Adding fault handlers. In many 8-bit and 16-bit microcontrollers, there are no fault exceptions. Although embedded applications can operate without any fault handlers, the addition of fault handlers can help an embedded system to recover from error (e.g., data corruption caused by voltage drop or electromagnetic interference).
One of the points mentioned earlier is the stack size. After porting to the ARM architecture, the required stack size could increase or decrease, depending on the application. The stack size might increase for the following reasons:
• Each register push takes 4 bytes of memory in ARM, whereas in 16-bit or 8-bit models, each register push takes 2 bytes or 1 byte.
• In ARM programming, local variables are often stored in stack, whereas in some architectures local variables might be defined in a separate data memory area.
On the other hand, the stack size could decrease for the following reasons:
• With 8-bit or 16-bit architecture, multiple registers are required to hold large data, and often these architectures have fewer registers compared to ARM, so more stacking would be required.
• The more powerful addressing mode in ARM means address calculations can be carried out on the fly without taking up register space. The reduction of register space used for an operation can reduce the stacking requirement.
Overall, the total RAM size required could decrease significantly after porting because in some architectures, such as the 8051, local variables are defined statically in data memory space rather on the stack.
So the memory space is used even when the function or subroutine is not running. On the other hand, in ARM processors, the local variables allocated on the stack only take up memory space when the function or subroutine is executing.
Also, with more registers available in the ARM processor's register bank compared to some other architectures, some of the local variables might only need to be stored in the register bank instead of taking up memory space.
The program memory requirement in the ARM Cortex-M0 is normally much lower than it is for 8-bit microcontrollers, and it is often lower than that required for most 16-bit microcontrollers.
So when you port your applications from these microcontrollers to the ARM Cortex-M0 microcontroller, you can use a device with smaller flash memory size. The reduction of the program memory size is often caused by the following:
• Better efficiency at handling 16-bit and 32-bit data (including integers and pointers)
• More powerful addressing modes
• Some memory access instructions can handle multiple data, including PUSH and POP
There can be exceptions. For applications that contains only a small amount of code, the code size in ARM Cortex-M0 microcontrollers could be larger compared to that for 8-bit or 16-bit microcontrollers for a couple of reasons:
• The ARM Cortex-M0 might have a much larger vector table because of more interrupts.
• The C startup code for ARM Cortex-M0 might be larger. If you are using ARM development tools like the Keil MDK or the RealView Development Suite, switching to the MicroLIB might help to reduce the code size.
Nonapplicable 8-/16-Bit Optimizations
Some optimization techniques used in 8-bit/16-bit microcontroller programming are not required on ARM processors. In some cases, these optimizations might result in extra overhead because of architectural differences. For example, many 8-bit microcontroller programmers use character data as loop counters for array accesses:
unsigned char i; /* use 8-bit data to avoid 16-bit processing * /
char a , b ;
for (i =0; i<10; i++) a[i]=b[i];
When compiling the same program on ARM processors, the compiler will have to insert a UXTB instruction to replicate the overflow behavior of the array index (“i”). To avoid this extra overhead, we should declare “i” as integer “int”, “int32 t”, or “uint32 t” for best performance.
Another example is the unnecessary use of casting. For example, the following code uses casting to avoid the generation of a 16 x 16 multiply operation in an 8-bit processor:
unsigned int x, y, z;
z = ( (char) x) * ( (char) y) ; /* assumed both x and must be
must be less than 256 * /
Again, such a casting operation will result in extra instructions in ARM architecture. Since Cortex-M0 can handle a 32 x 32 multiply with a 32-bit result in a single instruction, the program code can be simplified:
unsigned int x, y, z;
Z = X* y;
Migrating from the 8051 to the ARM Cortex-M0
In general, because most applications can be programmed in C entirely on the Cortex-M0, the porting of applications from 8-bit/16-bit microcontrollers is usually straightforward and easy. Here we will see some simple examples of the modifications required on the 8051:
Vector Table. In the 8051, the vector table contains a number of JMP instructions that branch to the start of the interrupt service routines. In some development environments, the compiler might create the vector table for you automatically.
In ARM, the vector table contains the address of the main stack pointer initial values and starting addresses of the exception handlers.
The vector table is part of the startup code, which is often provided by the development environment. For example, when creating a new project, the Keil MDK project wizard will offer to copy and add the default startup code, which contains the vector table (Table 21.6 below ).
Table 21.6: Vector Table Porting( To view larger image, click here)
Data Type. In some cases, we need to modify the data type so as to maintain the same program behavior (Table 21.7 below ).
Table 21.7: Data Type Change during Software Porting
Some function calls might also need to be changed if we want to ensure only single precision floating point is used (Table 21.8 below ).
Table 21.8: Floating Point C Code Change during Software Porting
Some special data types in 8051 are not available on the Cortex-M0: bit, sbit, sfr, sfr16, idata, xdata, and bdata.
Interrupt. Interrupt control code in 8051 are normally written as direct access to SFRs. They need to be changed to the CMSIS functions when ported to the ARM Cortex-M0. (Table 21.9 below ).
Table 21.9: Interrupt Control Change during Software Porting
The interrupt service routine also requires minor modifications. Some of the special directives used by the interrupt service routine need to be removed when the application code is ported to the Cortex-M0 (Table 21.10 below ).
Table 21.10: Interrupt Handler Change during Software Porting
Dealing with Sleep Mode
Entering of sleep mode is different too (Table 21.11 below ).
Table 21.11: Sleep Mode Control Change during Software Porting
In 8051, sleep mode can be entered by setting the IDL (idle) bit in PCON. In the Cortex-M0, you can use the WFI instruction, or use vendor-specific functions provided in the device driver library.
Used with permission from Newnes, a division of Elsevier.Copyright 2011, from “The definitive guide to the ARM Cortex-M0,“by Joseph Yiu. For more information about this title and other similar books,please visit www.elsevierdirect.com.