Monday, March 31, 2014

Embedded World's - Hello World - Toggling the GPIO.

Coding conventions followed in CMSIS:
  1. All the data types used in CMSIS is based out of stdint.h file. Data structures for core registers are defined in the CMSIS header core_cm0.h file.
  2. The core registers, peripheral registers and the CPU instructions follow capital naming convention. Eg: NVIC->ISER[0], GPIOA->ISRC,
  3. Peripheral access functions and interrupts use camel casing convention. Eg:  DrvGPIO_Open().
  4. CMSIS allows C++ like commenting method (eg: // this is used for commenting.) 

Finally, we are there on the main subject : Getting started!
So let us attempt to write our first example for toggling GPIO (Embedded world's "HELLO World" using Keil. It should be straight forward port for IAR and other tool chains, I guess though I cannot comment on that much since I have not had an experience using them for this controller.

Understanding the NU-LB-NUC140 board for the LED experiment:
The chip used on this EVM board is NUC140VE3CN. This board is powered by an external 12MHz crystal as well as 32.768Khz crystal for RTC applications. In this experiment we will attempt our first "Hello World" program by lighting up the LED for sometime and switching it off and continue doing that. The LEDs on this EVM board are connected on GPIO-PortC on pins -12,13,14 & 15. These LEDs are connected in SINK mode. Hence to turn on a LED you must place a ZERO on the GPIO pin and to switch off you must place a ONE.
  • NUC140 has 80 general purpose IOs.
  • 80 IOs are arranged in GPIOA, GPIOB, GPIOC, GPIOD, GPIOE.
  • On reset all IO pins stay in Quasi-Bidirectional mode.
  • IOs as outputs can support source/sink capability.

First Step: Creating your project in KEIL:
  1. Open KEIL -> New uVision Project -> <Select your custom folder> <select the chip as NUC140VE3CN.
  2. If asked for adding the startup file, please add to the project.
  3. For simplicity, click on "File Extensions" icon in KEIL and create two sub folders apart from source files folder. Name them as "Library" and "CMSIS" files. Move the files "startup_NUC1xx.s" to "CMSIS" folder. Open the "File Extensions" folder and add "startup_NUC1xx.c" and "core_cm0.c"  files into the "CMSIS" folder. 
    1. The "core_cm0.c" file is found in the location <systemInstalledDrive>:\Nuvoton\BSP Library\NUC100SeriesBSP_CMSIS_v1.05.003\CMSIS\CM0\CoreSupport. 
    2. The "startup_NUC1xx.c" file is found in the location: <systemInstalledDrive>:\Nuvoton\BSP Library\NUC100SeriesBSP_CMSIS_v1.05.003\CMSIS\CM0\DeviceSupport\Nuvoton\NUC1xx
  4. Add a main_gpio.c file to the "Source Files" folder. This is where your main() function will reside and do all the necessary work.
  5. As we studied in the earlier sections of CMSIS article, we will be using the library provided Nuvoton to program the GPIOs via the nuvoton library wrapper for easy portability rather than directly fiddling with the hardware registers. (Though it is perfectly fine if you wish to directly fiddle around the hardware registers like you do for the conventional 8-bit microcontrollers. I am using the Nuvoton library wrapper approach.
  6. Since in this example we are going to set the GPIOs, we will first include the GPIO library wrapper provided by Nuvoton by the name "DrvGPIO.c". This file is found in the location: <systemInstalledDrive>:\Nuvoton\BSP Library\NUC100SeriesBSP_CMSIS_v1.05.003\NuvotonPlatform_Keil\Src\Driver\
  7. Since the clock control is handled by the file "DrvSYS.c". This file should also be included similar to the "DrvGPIO.c". 
  8. It is important to let Keil's project settings know where your include library and core_cm0 headers files reside. Hence add the include paths by clicking on options for the project settings >> C/C++ >> Include Paths and give the following paths in the following order:
    • ./
    • ../../../CMSIS/CM0/CoreSupport
    • ../../../CMSIS/CM0/DeviceSupport/Nuvoton/NUC1xx
    • ../../../Include
    • ../../../Include/Driver
    • ../../../Include/NUC1xx-LB_002 
  9. Then select in options for project settings >> Linker and add "--map --first='startup_NUC1xx.o(RESET)' --datacompressor=off --info=inline --entry Reset_Handler" to Misc controls. Also untick "Use Memory Layout from target Dialog". (refer: http://www.keil.com/support/man/docs/uv4/uv4_dg_adsld.htm)
  10. One more setting to go, under Users, set "Run User Programs After build/rebuild"
    1. Under Run1:  Tick Run1 checkbox and argument as: fromelf --bin ".\obj\@L.axf" --output ".\obj\@L.bin"
    2. Under Run2: Tick Run2 checkbox and argument as:  fromelf --text -c ".\obj\@L.axf" --output ".\obj\@L.txt"
  11. Create a folder as obj in the working directory and select this folder for your output dump in Keil's project settings.
  12. Now let us get to the action.
To set up the chip, first you need to select the clock input to your chip. In this case it could you either internal oscillator or external crystal or PLL clock as per these configs:
E_SYS_CHIP_CLKSRC
Enumeration_identifier                     Value                                   Description
E_SYS_XTL12M                                  0                               Select External 12M Crystal
E_SYS_XTL32K                                  1                               Select External 32K Crystal
E_SYS_OSC22M                                2                               Select Internal 22M Oscillator
E_SYS_OSC10K                                3                                Select Internal 10K Oscillator
E_SYS_PLL                                        4                                Select PLL clock


Here is the main program:

#include "NUC1xx.h"
#include "Driver\DrvSYS.h"
#include "Driver\DrvGPIO.h"

void delay_loop(void)
 {
  uint32_t i,j;
    for(i=0;i<10;i++)   
    {
        for(j=0;j<60000;j++);
    }

 }

int main(void)
{
    //First set the clock input to the processor.
    //The choices are internal clock or external crystal.
   
    /* Unlock the protected registers */   
    UNLOCKREG();
    //We will use the internal 22.1184Mhz
    DrvSYS_SetOscCtrl(E_SYS_OSC22M,1);
   
    //wait till the clock is stable
    while (DrvSYS_GetChipClockSourceStatus(E_SYS_OSC22M) != 1);
   
    /* HCLK clock source. 0: external 12MHz; 7:internal 22MHz RC oscillator */
    DrvSYS_SelectHCLKSource(7);       
    /*lock the protected registers */
    LOCKREG();               
    
   /* HCLK clock frequency = HCLK clock source / (HCLK_N + 1) */
   /* Max divisor value: 0b1111 and min value: 0b0000 */ 
   DrvSYS_SetClockDivider(E_SYS_HCLK_DIV, 0);
                                                                                           
    //Configure GPIO - GPC- pin 12 as output port pin. LEDS are connected on 12, 13, 14, 15.
    DrvGPIO_Open(E_GPC, 12, E_IO_OUTPUT);
   
    while(1)
    {
        DrvGPIO_SetBit(E_GPC,12);
        delay_loop();
        DrvGPIO_ClrBit(E_GPC,12);
        delay_loop();
    }

}

Compile, build and burn the hex file using Nuvoton's ICP programming tool and see the led blinking. You can burn either the .hex file or the .bin file on the EVM board and check your program.

Slight Tweaks for replacing the delay_loop() : Exploring System Tick  - a 24bit Timer available on ARM CortexM0.

  • SysTick provides a simple, 24-bit clear-on-write, decrementing, wrap-on-zero counter with a flexible control mechanism. It comprises of  SYST_CVR, SYST_RVR and SYST_CSR registers.
  • SYST_CVR is the Current Value Register while SYST_RVR is the Reload Value Register.
  • SYST_CSR is the Systick timer control and status register.
  • Systick uses either the same CORE_CPU clock or any of the external clock. To use the CORE_CPU clock, the value of bit named CLKSRC in SYST_CSR should be "ONE".
  • If CLKSRC in SYST_CSR is "ZERO" then based on STCLK_S in CLKSEL0 register decides the external clock speed to be used for SYSTICK timer.

To replace the above delay_loop() , we made a new function:
void systemDelay(void) //1 sec delay if running out of 12Mhz.
{
     //Considering 1tick = 12Mhz so for 1 sec, reload value = 12000000.
      SysTick->LOAD = 12000000; //Reload value in register for SysTick for 1 sec delay
      SysTick->VAL = (0x000000); //default value of SysTick Current value register.

     
//Switch on sysTick timer.
     //System Clock source is (optional) external reference clock

     SysTick->CTRL = (1<<SysTick_CTRL_ENABLE_Pos);

     /* Waiting for down-count to zero . 16th bit in SYST_CSR */
     while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);
}




One more important point to remember:

  • You cannot have systick timer source using a different clock source as compared to the main system clock even though it is configurable. Meaning, if your CPU clock is out of external crystal then you cannot select your systick to be coming out internal oscillator at the same time, when I experimented on this board. (I thought it should be possible!). Same way, if you ran the CPU over internal oscillator then trying to run the systick timer clock to be running out of external crystal does not work.
  • I decided to use pre-processor directive for this reason as shown below. This code should be part of the protected register write along with setting the CPU clock.
#if CRYSTAL
        DrvSYS_SelectSysTickSource(0);     //Select External 12Mhz as the system Tick's clock    #else
        DrvSYS_SelectSysTickSource(7);    //Select Internal 22.1184Mhz/2 as the system Tick's
#endif


So the above code could be re-written to replace the delay_loop with systemTick timer in this manner (main_gpio.c)
-------------------------------------------------------------------------------------------------------------------------------

CMSIS files for Nuvoton's NUC 140


Contents of the CMSIS file structure: 
  • core_cm0.h : contains cortex-M0's global declarations & definitions as well as static function definitions.
  • core_cm0.c : contains cortex-M0's global definitions.
  • <device.h> [in this case: NUC1xx.h] : Top level device specific header file containing device specific bit definitions and structure/union definitions. This file includes core_cm0.h and system_NUC1xx.h files. This file should be included while writing application code.
  • system_<device>.h  [in this case system_NUC1xx.h] : This file contains device specific declarations.
  • system_<device>.c [in this case system_NUC1xx.c] : This file contains device specific definitions like SystemCoreClockUpdate() and systemInit() calls.


Exploring the NUC1xx.h file :
  • In this file, the parameter _MPU Present  0 indicates there is no memory protection unit while the other parameter __NVIC_PRIO_BITS  2 informs the user that there can be maximum 2 priority bits to set.
  • The __Vendor_SysTickConfig defined parameter has the default setting as 0. When this macro is set to 1, the SysTickConfig() function in the core_cm0.h is excluded. If it is set to 1 then in that case file <device>.h [here NUC1xx.h] must contain a vendor [Nuvoton] specific implementation of this function.

Compiler support built into CMSIS: Currently three main compilers are supported by CMSIS namely, armgcc  from ARM REAL view, iccarm from IAR EWARM and gcc from GNU compiler collection. All the functions in the core peripheral access layer are reentrant and can be called from different interrupt service routines. The exception handers are given a suffix __Handler while the external interrupt handlers are given a suffix __IRQHandler.

What is CMSIS?

CMSIS is an acronym for Cortex Microcontroller Software Interface Standard.


CMSIS is an abstract layer that supports developers and vendors in creating reusable software components for ARM Cortex-M based systems. This layer is useable for Cortex-M0,M0+, M1 M3/M4 layers as well.


A brief glance into Cortex-M0,M0+, M1, M3 and M4 architectures:
  • Cortex-M0: The ARM Cortex™-M0 processor is the smallest ARM processor available. The exceptionally small silicon area, low power and minimal code footprint of the processor enables developers to achieve 32-bit performance at an 8-bit price point.
    • Supports 3-stage pipeline, thumb2, hardware-single-cycle (32x32) multiply hardware.
    • Supports 1 NMI and 32 physical interrupts.
    • Has only 56 instructions and has 'C-friendly' architecture.  
  • Cortex-M0+: The ARM Cortex™-M0+ processor is an adaption of Cortex-M0 but with more improved performance and reduced energy footprint.
    • Supports 2-stage pipeline, thumb2, hardware-single-cycle(32x32) multiply hardware.
  • Cortex-M1: The ARM Cortex™-M1 processor is the first ARM processor designed specifically for implementation in FPGAs. 
    • Supports 3-stage pipeline, thumb2 and big & little endian configuration. 
  • Cortex-M3: The ARM Cortex™-M3 processor is the industry-leading 32-bit processor for highly deterministic real-time applications, specifically developed to enable partners to develop high-performance low-cost platforms for a broad range of devices including microcontrollers, automotive body systems, industrial control systems and wireless networking and sensors.
    • The Cortex-M3 NVIC is highly configurable at design time to deliver up to 240 system interrupts with individual priorities, dynamic reprioritization and integrated system clock.
    • Supports 1 NMI  and 240 physical interrupts with 8 to 256 level priorities.
    • Supports hardware divide, single cycle-multiply and saturated math support.
  •  Cortex-M4: The ARM Cortex™-M4 processor  is the latest embedded processor by ARM specifically developed to address digital signal control markets that demand an efficient, easy-to-use blend of control and signal processing capabilities.
    • Supports 3-stage pipeline with branch prediction and thumb2.
    • Supports hardware-divide, 8/16 bit SIMD arithmetic.
    • Supports single precision floating point unit.
    • Supports Memory protection unit and deterministic operations.
 

Continuing further on CMSIS:

In order to reduce cost and software complexity and the fact that there are too rapid changes on the technology front, CMSIS provides a uniform hardware abstraction layer that governs the way we write  and debug software by ensuring software re-useability.

CMSIS structure consists of three layers:
  1. Core peripheral Access Layer (CPAL)
  2. Middleware Access Layer (MAL)
  3. Device peripheral Access Layer (DPAL)
CMSIS functional Flow (Courtesy: https://www.doulos.com)



Functionality of each CMSIS layer:

Core Peripheral Access Layer: This layer is provided by ARM. The lowest level defines addresses, and access methods for common components and functionality that exists in every Cortex-M system. Access to core registers, NVIC, debug subsystem is provided by this layer.

Middleware Access Layer:This layer is defined by ARM, but is also adapted by silicon vendors for their respective devices.The Middleware Access Layer defines a common API for accessing peripherals.
  
Device Peripheral Access Layer:This layer is provided by the silicon vendor and contains the hardware register addresses and other device specific  access functions.