One significant difference between the two architectures is the balance between hardware and software in interrupt dispatch.
PIC18 devices (with the priority scheme enabled) have two interrupt vectors. All high-priority interrupts are assigned to one, all low priority interrupts to the other. Each interrupt handler is responsible for determining which of the possible sources has raised an interrupt (the Interrupt Flag bits must be individually checked to work this out). Once the source has been identified, a specific handler can be involved to handle the event.
The NVIC on Cortex-M3 devices handles all this automatically. All interrupt sources have separate vectors defined in the vector table. In parallel with saving context on the stack, the NVIC reads the relevant vector address from the table and directs program execution directly to the start of the correct handler.
1. Writing interrupt handlers
Interrupt handler routines for PIC devices must be identified in C source code using a #pragma directive.
When writing interrupt handlers in PIC assembler, there are constraints (e.g. cannot e.g. access arrays using calculated index, call other functions, perform complex math, access ROM variables etc). These must be followed for correct operation.
If the interrupt priority scheme is not used only one handler is required; two are required if the priority scheme is used.
Cortex-M3 interrupt handlers are standard C functions and there are no special C coding rules for them.
2. Vector table generation
The interrupt handlers must be installed by putting a GOTO instruction at the vector location. Typically this is done with a short section of inline assembler within the C application. If the priority scheme is used, two vectors are required.
On Cortex-M3 devices, the vector table is typically defined by an array of C function pointers. This is then located at the correct address by the linker.
3. Interrupt configuration
In order to enable a PIC interrupt, the following must be set correctly:
– Specific Interrupt Enable bit
– Global Interrupt Enable bit
– Global High/Low Priority Interrupt Enable bit (if priority is used)
– Peripheral Interrupt Enable bit (for peripheral interrupts)
On Cortex-M3 devices, the NVIC must be configured appropriately:
– Interrupt Set Enable register
– PRIMASK must be clear to enable interrupts
This is achieved using CMSIS intrinsic functions __enable_irq().and NVIC_EnableIRQ(). See the CMSIS documentation for more detail. CMSIS functions are also available for setting and clearing pending interrupts, checking the current active interrupt and configuring priority.
It is common, when programming for PIC devices, to use NOP instructions as a way of consuming time. The execution time of NOP instructions is easily determined from the system clock speed. When developing for Cortex-M3 devices this cannot be relied on as the pipeline is free to “fold out” NOP instructions from the instruction stream. When this happens, they do not consume time at all.
Deterministic delays in Cortex-M3 systems must therefore make use of a timer peripheral.
4.6.1 Standard peripherals
On the Cortex-M3 it is usual to define a structure covering the System Control Space. All of the system control registers are located in this region. The standard peripherals (NVIC, SysTick etc.) are all controlled via registers in this area.
4.6.2 Custom peripherals
Custom peripherals on Cortex-M3 microcontrollers are generally memory-mapped within the defined peripheral region in the memory map. The usual method of access to these is to define C structures which describe the relevant registers. These can then be located at absolute addresses at link time via the scatter control file.
ANSI C cannot generate the WFI and WFE instructions directly. Instead, you should use the CMSIS intrinsic functions __WFE() and __WFI() in your source code. If suitable device-driver functions are provided by the vendor, then these should be used in preference.
· Don’t bother with static parameters or overlays. Cortex-M3 does not suffer from data RAM size constraints like PIC.
· Unless memory size is a constraint, don’t bother with small data types. They are less efficient on Cortex-M3.
· There is no need to declare objects as “near” or “far”.
· There is no need to specify which “bank” variables are located in.
· No need to designate interrupt handlers using any special keywords. Though it is regarded as good programming practice to declare interrupt handlers using the __irq keyword when using the ARM/Keil tools to develop for Cortex-M3.
· It is unlikely that you will encounter any data alignment problems but the __packed keyword should be used to resolve any which do arise.
· There is no need to specify any particular memory model.
· Any inline assembler in your PIC source will need to be re-written. Typically it should be rewritten in C rather than translated to inline ARM assembler – this will be easier and more portable.
· Any #pragma directives will need to be either removed or replaced. Those associated with associated with placement code or data in C source code must be removed. Those marking interrupt handlers should also be removed and optionally the functions marked with __irq instead.
· There is no facility in Cortex-M3 for specifying any initial conditions as with the PIC configuration words which are automatically loaded on reset. Any code associated with this should be removed. All setup of Cortex-M3 core and peripherals needs to be performed in software following reset.