Basics of the Cortex MCU Software Interface Standard: Part 2 – CMSIS core structure

Editor’s Note: Excerpted from The Designer's Guide to the Cortex-M Processor Family , by Trevor Martin, the author provides an in-depth tutorial on the ARM Cortex Microcontroller Software Interface Standard (CMSIS) core structure.

System Code
The reset handler calls the SystemInit() function, which is located in the CMSIS system device.c file. This code is delivered by the silicon manufacturer and it provides all the necessary code to configure the microcontroller out of reset. Typically this includes setting up the internal phase-locked loops, configuring the microcontroller clock tree and internal bus structure, enabling the external bus if required, and switching on any peripherals held in low-power mode.

The configuration of the initializing functions is controlled by a set of #defines located at the start of the module. This allows you to customize the basic configuration of the microcontroller system peripherals. Since the SystemInit() function is run when the microcontroller leaves reset the microcontroller and the Cortex-M processor will be in a running state when the program reaches main. In the past, this initializing code was something you would have had to write for yourself or crib from example code. The SystemInit() function does save you a lot of time and effort.

The SystemInit() function also sets the CMSIS global variable SystemCoreClock to the CPU frequency. This variable can then be used by the application code as a reference value when configuring the microcontroller peripherals. In addition to the SystemInit() function the CMSIS system file contains an additional function to update the SystemCoreClock variable if the CPU clock frequency is changed on the fly.

The function SystemCoreClockUpdate() is a void function that must be called if the CPU clock frequency is changed. This function is tailored to each microcontroller and will evaluate the clock tree registers to calculate the new CPU operating frequency and change the SystemCoreClock variable accordingly. Once the SystemInit() function has run and we reach the application code we will need to access the CMSIS core functions. This framework is added to the application modules through the microcontroller-specific header file.

Device Header File
The header file first defines all of the microcontroller special function registers in a CMSIS standard format. A typedef structure is defined for each group of special function registers on the supported microcontroller. In the code below, a general GPIO typedef is declared for the group of GPIO reregisters. This is a standard typedef but we are using the IO qualifiers to designate the type of access granted to a given register.

     typedefstruct
     {
     __IOuint32_t MODER;
          /*!< GPIOportmoderegister, Addressoffset:0x00 */
     __IOuint32_t OTYPER;
          /*!< GPIOportoutput typeregister, Addressoffset:0x04*/
     __IOuint32_t OSPEEDR;
          /*!< GPIOportoutput speedregister, Addressoffset:0x08*/
     __IOuint32_tPUPDR;
          /*!< GPIOportpull-up/pull-downregister,
          Addressoffset:0x0C*/
     __IOuint32_t IDR;
          /*!< GPIOportinput dataregister, Addressoffset:0x10*/
     __IOuint32_t ODR;
          /*!< GPIOportoutput dataregister, Addressoffset:0x14*/
     __IOuint16_tBSRRL;
          /*!< GPIOportbitset/resetlowregister,
          Addressoffset:0x18*/
     __IO uint16_tBSRRH;
          /*!< GPIO port bit set/reset high register,
          Addressoffset: 0x1A */
     __IOuint32_tLCKR;  
          /*!< GPIOportconfigurationlockregister, 
          Addressoffset:0x1C*/
     __IO uint32_t AFR[2];
          /*!< GPIO alternate function registers,    
          Addressoffset:0x24-0x28 */
     }GPIO_TypeDef;

Next #defines are used to lay out the microcontroller memory map. First, the base address of the peripheral special function registers is declared and then offset addresses to each of the peripheral busses and finally an offset to the base address of each GPIO port.

      #definePERIPH_BASE ((uint32_t)0x40000000)
      #defineAPB1PERIPH_BASE PERIPH_BASE
      #defineGPIOA_BASE (AHB1PERIPH_BASE 10×0000)
      #defineGPIOB_BASE (AHB1PERIPH_BASE 10×0400)
      #defineGPIOC_BASE (AHB1PERIPH_BASE 10×0800)
      #defineGPIOD_BASE (AHB1PERIPH_BASE 10x0C00)

Then the register symbols for each GPIO port can be declared.

      #defineGPIOA ((GPIO_TypeDef *) GPIOA_BASE)
      #defineGPIOB ((GPIO_TypeDef *) GPIOB_BASE)
      #defineGPIOC ((GPIO_TypeDef *) GPIOC_BASE)
      #defineGPIOD ((GPIO_TypeDef *) GPIOD_BASE)

Then in the application code we can program the peripheral special function registers by accessing the structure elements.

     void LED_Init (void) {
     RCC- >AHB1ENR | = ((1UL << 3) );
          /* Enable GPIOD clock */
     GPIOD- >MODER & = ~((3UL << 2*12) |
          (3UL << 2*13) |
          (3UL << 2*14) |
          (3UL << 2*15) ); /* PD.12..15 is output */
     GPIOD- >MODER | = ((1UL << 2*12) |   
          (1UL << 2*13) |
          (1UL << 2*14) |
          (1UL << 2*15) ) ;

The microcontroller include file provides similar definitions for all of the on-chip peripheral special function registers. These definitions are created and maintained by the silicon manufacturer and as they do not use any non-ANSI keywords the include file may be used with any C compiler. This means that any peripheral driver code written to the CMSIS specification is fully portable between CMSIS compliant tools. The microcontroller include file also provides definitions of the interrupt channel number for each peripheral interrupt source.


     WWDG_IRQn =0, /*!< WindowWatchDoginterrupt */
     PVD_IRQn =1, /*!< PVD throughEXTIlinedetection interrupt */
     TAMP_STAMP_IRQn =2, /*!< Tamper and TimeStamp interrupts throughthe EXTIline */
     RTC_WKUP_IRQn =3, /*!< RTC Wakeup interrupt throughthe EXTIline */
     FLASH_IRQn =4, /*!< FLASHglobal interrupt */
     RCC_IRQn =5, /*!< RCC globalinterrupt */
     EXTI0_IRQn =6, /*!< EXTILine0interrupt */
     EXTI1_IRQn =7, /*!< EXTILine1interrupt */
     EXTI2_IRQn =8, /*!< EXTILine2interrupt */
     EXTI3_IRQn =9, /*!< EXTILine3interrupt */
     EXTI4_IRQn =10, /*!< EXTILine4interrupt
*/

In addition to the register and interrupt definitions, the silicon manufacturer may also provide a library of peripheral driver functions. Again as this code is written to the CMSIS standard it will compile with any suitable development tool. Often these libraries are very useful for getting a project to work quickly and minimizing the amount of time you have to spend writing low-level code. However, they are often very general libraries that do not yield the most optimized code.

So, if you need to get the maximum performance/minimal code size, you will need to rewrite the driver functions to suit your specific application. The microcontroller include file also imports up to five further include files. These include stdint.h, a CMSIS core file for the Cortex processor you are using. A header file System_device.h is also included to give access to the functions in the system file. The CMSIS instruction intrinsic and helper functions are contained in two further files: core_cminstr.h and core_cmfunc.h.

If you are using the Cortex-M4 an additional file, core_CM4_simd.h, is added to provide support for the Cortex-M4 SIMD instructions. As discussed earlier, the stdint.h file provides the MISRA C types that are used in the CMSIS definitions and should be used through your application code.

CMSIS Core Header Files
The device file imports the Cortex-M processor which are held intheir own include files. There are a small number of defines that areset up for a given device. These can be found in the main processorinclude file.  The processor include file also imports the CMSIS headerfiles, which contain the CMSIS core helper functions. The helperfunctions are split into the groups shown below.

Table 3: CMSIS Configuration Values

The NVIC group provides all the functions necessary to configure theCortex-M interrupts and exceptions. A similar function is provided toconfigure the systick timer and interrupt. The CPU register groupallows you to easily read and write to the CPU registers using the MRSand MSR instructions.

Any instructions that are not reachable by the C language areprovided by dedicated intrinsic functions and are contained in the CPUinstructions group. An extended set of intrinsics are also provided forthe Cortex-M4 to access the SIMD instructions. Finally, some standardfunctions are provided to access the debug instrumentation trace.

Interrupts and Exceptions
Management of the NVIC registers may be done by the functionsprovided in the interrupt and exception group. These functions allowyou to setup an NVIC interrupt channel and manage its priority as wellas interrogate the NVIC registers during runtime.

A configuration function is also provided for the systick timer.So, for example, to configure an external interrupt line we first needto find the name for the external interrupt vector used in the startupcode of the vector table.

Table 4: CMSIS Function Groups


Table 5: CMSIS Interrupt and Exception Group


Table 6: CMSIS Systick Function

      DCD FLASH_IRQHandler ; FLASH
     DCD RCC_IRQHandler ; RCC
     DCD EXTI0_IRQHandler ; EXTILine0
     DCD EXTI1_IRQHandler ; EXTILine1
     DCD EXTI2_IRQHandler ; EXTILine2
     DCD EXTI3_IRQHandler ; EXTILine3
     DCD EXTI4_IRQHandler ; EXTILine4
     DCD DMA1_Stream0_IRQHandler ; DMA1Stream 0

So for external interrupt line 0 we simply need to create a void function duplicating the name used in the vector table.

      void EXTI0_IRQHandler (void);

This now becomes our interrupt service routine. In addition, we mustconfigure the microcontroller peripheral and NVIC to enable theinterrupt channel. In the case of an external interrupt line, thefollowing code will setup Port A pin 0 to generate an interrupt to theNVIC on a falling edge.

     AFIO- > EXTICR[0] &= BAFIO_EXTICR1_EXTI0; /* clear used pin */
     AFIO- > EXTICR[0] |= AFIO_EXTICR1_EXTI0_PA; /*set PA.0 to use */
     EXTI- > IMR |=  EXTI_IMR_MR0; /* unmask interrupt */
     EXTI- > EMR & = ~EXTI_EMR_MR0; /* no event */
     EXTI- > RTSR & = ~EXTI_RTSR_TR0; /* no rising edge trigger */
     EXTI- > FTSR |= EXTI_FTSR_TR0; /*set falling edge trigger */

Next we can use the CMSIS functions to enable the interrupt channel.

     NVIC_EnableIRQ(EXTI0_IRQn);

Here we are using the defined enumerated type for the interruptchannel number. This is declared in the microcontroller header file.Once you get a bit familiar with the CMSIS core functions, it becomeseasy to intuitively work out the name rather than having to look it upor look up the NVIC channel number.

We can also add a second interrupt source by using the systickconfiguration function, which is the only function in the systickgroup.

     uint32_t SysTick_Config(uint32_tticks)

This function configures the countdown value of the systick timerand enables its interrupt so an exception will be raised when its countreaches zero. Since the SystemInit() function sets the global variableSystemCoreClock with the CPU frequency that is also used by thesysticktimer we can easily setup the systick timer to generate a desiredperiodic interrupt. So a 1ms interrupt can be generated as follows:

     Systick_Config(SystemCoreClock/1000);

Again we can look up the exception handler from the vector table

      DCD 0                             ; Reserved
     DCD PendSV_Handler                ; PendSVHandler
     DCD SysTick_Handler               ; SysTickHandler

and create a matching C function:

     void SysTick_Handler(void);

Now that we have two interrupt sources we can use other CMSISinterrupt and exception functions to manage the priority levels. Thenumber of priority levels will depend on how many priority bits havebeen implemented by the silicon manufacturer. For all of the Cortex-Mprocessors we can use a simple “flat” priority scheme where zero is thehighest priority. The priority level is set by

     NVIC_SetPriority(IRQn_Type IRQn, uint32_tpriority);

The set priority function is a bit more intelligent than a simplemacro. It uses the IRQn NVIC channel number to differentiate betweenuser peripherals and the Cortex processor exceptions.

This allows it to program either the system handler priorityregisters in the SCB or the interrupt priority registers in the NVICitself. The set priority function also uses the NVIC_PRIO_BITSdefinition to shift the priority value into the active priority bitsthat have been implemented by the device manufacturer.

     _STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
     {
          if(IRQn ,0) {
              SCB- > SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 -_NVIC_PRIO_BITS))& 0xff); } /*set Priority for Cortex M SystemInterrupts */
          else{
               NVIC- > IP[(uint32_t)(IRQn)] = ((priority << (8 - _NVIC_PRIO_BITS)) & 0xff);
               /*set Priority for device specific Interrupts */
     {

However, for the Cortex-M3 and Cortex-M4 we have the option to setpriority groups and subgroups, Depending on the number of priority bitsdefined bythe manufacturer, we can configure priority groups andsubgroups.

     NVIC_SetPriorityGrouping();

To set the NVIC priority grouping you must write to the AIRCregister. This register is protected by its VECTKEY field. In order toupdate this register, you must write 0x5FA to the VECTKEY field. The SetPriorityGrouping function provides all the necessary code to dothis.

     __STATIC_INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
     {
     uint32_t reg_value;
     uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07);
     /* only values 0..7 are used */
     reg_value = SCB- >AIRCR; /* read old register configuration */
     reg_value &= ~(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk);
     /* clear bits to change */
     reg_value = (reg_value | ((uint32_t)0x5FA << SCB_AIRCR_VECTKEY_Pos) |
     /* insert write key and priortygroup*/
          (PriorityGroupTmp << 8));
     SCB.AIRCR = reg_value;
     }

The interrupt and exceptiongroup also provides a system reset function that will generate a hardreset of the whole microcontroller.

     NVIC_SystemReset (void);

This function writes to bit 2 of the “Application Interrupt ResetControl” register. This strobes a logic line out of the Cortex-M coreto the microcontroller reset circuitry which resets the microcontrollerperipherals and the Cortex-M core. However, you should be a littlecareful here as the implementation of this feature is down to themicrocontroller manufacturer and may not be fully implemented.

So if you are going to use this feature you need to test it first.Bit zero of the same register will do a warm reset of the Cortex-Mcore,that is, force a reset of the Cortex-M processor but leave themicrocontroller registersconfigured. This feature is normally used bydebug tools.

CMSIS Core Register Access
The next group of CMSIS functions gives you direct access to theprocessor core registers. These functions provide you with the abilityto globally control the NVIC interrupts and set the configuration ofthe Cortex-M processor into its more advanced operating mode. First, wecan globally enable and disable the microcontroller interrupts withthe following functions:

      __set_PRIMASK (void);
     __set_FAULTMASK(void);
     __enable-IRQ
     __enable_Fault-irq
     __set_BASEPRI()


Tables 7a and 7b: CMSIS CPU Register Functions

While all of these functions are enabling and disabling interruptsources they all have slightly different effects. The __set_PRIMASK()function and the enable_IRQDisable_IRQ functions have the same effectin that they set and clear the PRIMASK bit, which enables and disablesall interrupt sources except the hard fault handler and the nonmaskableinterrupt.

The __set_FAULTMASK() function can be used to disable all interruptsexcept the nonmaskable interrupt. We will see later how this can beuseful when we want to bypass the MPU. Finally, the __set_BASEPRI()function sets the minimum active priority level for user peripheralinterrupts. When the base priority register is set to a nonzero levelthe interrupt at the same priority level or lower will be disabled.

These functions allow you to read the PSR and its aliases.You canalso access the control Register to enable the advanced operating modesof the Cortex-M processor as well as Explicitly setting the stackpointer values. A dedicated function is also provided to access theFloating point status control(FPSC) register, if you are using theCortex-M4.

CMSIS Core CPU Intrinsic Instructions
The CMSIS core header also provides two groups of standardizedintrinsic functions. The first group is common to all Cortex-Mprocessors and the second group provides standard intrinsic for theCortex-M4 SIMD instructions.

The CPU intrinsics provide direct access to Cortex-M processorinstructions that are not directly reachable from the C language. Whilemany of their functions can be achieved by using multiple instructionsgenerated by high-level C code you can optimize your code by thejudicious use of these instructions.

With the CPU intrinsics we can enter the low-power modes using the__WFI() and _WFE() instructions. The CPU intrinsics also provideaccess to the saturated math Instructions. The intrinsic functions alsogive access to the execution of barrier instructions that ensurecompletion of a data write or instruction for execution beforecontinuing with the next instruction.

The next group of instruction intrinsics is used to guaranteeexclusive access to a memory region by one region of code. Theremainder of the CPU intrinsics support single-cycle data manipulationfunctions such as the rotate and RBIT instructions.

Table 8: CMSIS Instruction Intrinsics

CMSIS SIMD Intrinsics

The finalgroup of CMSIS intrinsics provide direct access to the Cortex-M4 SIMDinstructions. The SIMD instructions provide simultaneous calculationsfor two 16-bit operations or four 8-bit operations. This greatlyenhances any form of repetitive calculation over a data set, as in adigital filter.

CMSIS Core Debug Functions
The final group of CMSIS core functions provides enhanced debugsupport through the CoreSight instrumentation trace. The CMSIS standardhas two dedicated debug specifications,   However, the CMSIS corespecification contains some useful debug support. As part of theirhardware debug system, the Cortex-M3 and Cortex-M4 provide aninstrumentation trace unit (ITM).

This can be thought of as a debug UART that is connected to aconsole window in the debugger. By adding debug hooks (instrumenting)into your code it is possible to read and write data to the debuggerwhile the code is running.  

Part 1: CMSIS Specification

Trevor Martin is Senior TechnicalSpecialist within Hitex UK. Over the 20 years he has worked at HitexUK, hevor has worked with a wide range of microcontrollers andassociated development tools. Since the launch of the Cortex-M3processor in 2004, Trevor has contributed numerous articles andapplication notes for many of the leading Cortex-M basedmicrocontrollers.

Used with permission from Newnes, an imprint of Elsevier, Copyright 2013, this article was excerpted from The Designer's Guide to the Cortex-M Processor Family by Trevor Martin.  

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.