An ARM Up

arm

On a recent project I needed a µC in a SOIC-20 package to fill a spot on a self-designed PCB. The project started with an ATtiny4313, but the software quickly outgrew the limited 4K flash program memory. I upgraded to the ATtiny1634, with 16K flash, but I noticed it’s pin-outs are completely different, necessitating a redesign of the PCB traces.

Since this encompassed more work than I originally planned, and if I was going to rework the PCB, I might as well look for alternatives outside of the ATMEL family. That’s when I stumbled upon the ARM LPC812 µC.

While similar to the ATtiny1634 in many respects, the LPC812 has some very compelling features. They both have 16kB flash program memory, the standard USART, I2c, SPI, and ADC peripherals. But that’s about where the similarities end.

The LPC812 is a 32-bit ARM Cortex-M0+ processor, while the ATtiny1634 is only 8-bit. The ARM can operate up to 30MHz, has 4kB SRAM, has switchable pin functions and comes in a variety of packages (DIP8, TSSOP16, and TSSOP20). If a TSSOP20 package is acceptable, then the LPC824 is available with 32kB flash with even more features!

However, the flash memory used for the program is not really a limiting factor, as there are provisions for “In Application Programming” of the flash. This feature enables one to fill the program memory from off-chip sources allowing for program/data expansion, EEPROM emulation (the µC has no EEPROM) and firmware updating.

No doubt, NXP has aimed the ARM’s 32-bit guns directly at the 8-bit chips with the LPC8xx and LPC11xx families. I must admit, I have been looking for an excuse to learn ARM Cortex-M programming for some time, this was the justification I needed. What follows, are my trials in progressing into ARM programming.

Hardware
I could have simply purchased a demo board to start my learning process. Two decent candidates are the LPC812-LPCXpresso Board from NXP, which comes with a removable LPC-Link programmer/debugger interface, and the LPC800/812 MAX board from Embedded Artists which is Arduino Duo “shield compatible”.

LPC812 Breadboard

However, those options wouldn’t be adventurous enough, so I opted to construct a breakout board myself. After a brief internet search, I found eagle files for a very small and basic board online. I quickly ordered 3 PCBs for $3.30 from OSHPark, and the necessary components from mouser.com. In less than a week I had the first example assembled.

I started the assembly process by hand soldering the SMT capacitors, resistors and power regulator on the bottom of the board. Next I used a drag-solder technique to add the LPC812 chip. Liberal use of flux, limited amounts of solder, and post-cleaning with a wick made quick and easy work of the TSSOP-16 package. Next, I added the two momentary buttons (mouser part #506-147873-2, but by the eagle footprint these appear to be a SparkFun part). I finished off the board by adding the three pin headers. Additional information on the board can be found here.

Software

There are many options available for programming environments. I experimented with the following 3 packages that appealed to me for various reasons:

  • LPCxpresso IDE
  • IAR Systems Embedded Workbench
  • Keil uVision

All of these packages have licenses that allow free use. But they come with limitations. Keil and IAR Systems limit program size to 32K, while LPCxpresso is limited to 256K.

LPCxpresso has good documentation, forum support, wide-spread use, and both video and written tutorials. It’s an Eclipse-based environment that uses GCC. However, I found it slightly overwhelming and slow at times.

I’ve had previous experience using Keil uVision from a college course. Keil is a product of ARM itself and uses a version of the ARM compiler suite. I could only find a written tutorial for Keil and the LPC8xx. And I found the tutoral barely adequate. Keil incorporates a simulator for code testing, which I find highly useful. However, I find Keil klunky and it’s overall appearance looks somewhat dated.

In my opinion, IAR Embedded Workbench is the easiest to use, integrates a great simulator, and has an excellent series of online video tutorials produced by Quantum Leaps. While the tutorials are for a TI-based ARM demo board, they are somewhat applicable to the LPC, and will quickly get you up and running with the IDE. IAREW uses a proprietary compiler suite also.

j-link
My Segger J-Link-Edu SWD/JTAG debugger hasn’t arrived yet, so none of my testing has been performed using a debugger. My mind isn’t made up yet as to which IDE will become my favorite.

One last and possibly better option is the free and unlimited CooCox IDE along with the inexpensive ColinkEx debugger. But I haven’t gotten around to downloading and playing with this suite.

Books
I have found no books suitable for learning programming on an ARM LPC8xx µC.

Blink Program
No introduction to the ARM µC would be complete without the ubiquitous Blinking LED program, the “Hello World” for µC. There are many examples of LED blinking program floating around the internet. However, the vast majority of them incorporate ARM’s CMSIS library.

In my opinion, CMSIS is nearly worthless in the embedded world. CMSIS is a hardware abstraction layer inserted between the µC and your program. This typically increases complexity, increases code size and needlessly obfuscates a program. Furthermore, each manufacturer has specific versions of CMSIS for their µCs, which completely destroys the goal of portability.

I set out to write my blink program at the most basic level and not incorporate CMSIS. Sometimes, this coding technique is referred to as, “bare metal programming.” I wrote a small include file for GPIO access and clock initialization. Over the next few weeks I intend to expand this file with additional features of the LPC812, negating having to add the needless abstraction of CMSIS to my programs.

This version of Blink runs on the IAREW and LPCxpresso IDEs (updated 2/6/15):

//----------------------------------------------------------------------------
// Name:    Blink.c
// Purpose: LED Flasher
//----------------------------------------------------------------------------
#include <stdint.h>
#include "LPC812.h" //bare metal version

//12MHz
#define SYSTEM_CORE_CLOCK 12000000UL

//millisecond counter
volatile uint32_t millis = 0;

//our systick interrupt handler
void SysTick_Handler(void) {
  millis++;
}

//delay (ms)
void delay_ms(uint32_t ms) {
  uint32_t now = millis;
  while ((millis - now) < ms);
}

int main(void) {
  //init peripherals
  SystemInit();
  //setup systick clock interrupt @1ms
  SysTickConfig(SYSTEM_CORE_CLOCK/1000);
  //init gpio port
  GpioInit();
  //make pin an output
  GPIO_DIR |= (1<<9); //LPC812 TSSOP16 pin #10
  //loop forever flashing lED @ 1 second interval
  while (1) {
    GPIO0_9 = 1;    //set pin high
    delay_ms(1000);
    GPIO0_9 = 0;    //set pin low
    delay_ms(1000);
  }
}

My header file (updated 2/7/15):

//LPC812.h

// GPIO_PORTs
#define GPIO_BASE            0xA0000000
#define GPIO0_0              (*((volatile unsigned char *)(GPIO_BASE + 0x00)))
#define GPIO0_1              (*((volatile unsigned char *)(GPIO_BASE + 0x01)))
#define GPIO0_2              (*((volatile unsigned char *)(GPIO_BASE + 0x02)))
#define GPIO0_3              (*((volatile unsigned char *)(GPIO_BASE + 0x03)))
#define GPIO0_4              (*((volatile unsigned char *)(GPIO_BASE + 0x04)))
#define GPIO0_5              (*((volatile unsigned char *)(GPIO_BASE + 0x05)))
#define GPIO0_6              (*((volatile unsigned char *)(GPIO_BASE + 0x06)))
#define GPIO0_7              (*((volatile unsigned char *)(GPIO_BASE + 0x07)))
#define GPIO0_8              (*((volatile unsigned char *)(GPIO_BASE + 0x08)))
#define GPIO0_9              (*((volatile unsigned char *)(GPIO_BASE + 0x09)))
#define GPIO0_10             (*((volatile unsigned char *)(GPIO_BASE + 0x0a)))
#define GPIO0_11             (*((volatile unsigned char *)(GPIO_BASE + 0x0b)))
#define GPIO0_12             (*((volatile unsigned char *)(GPIO_BASE + 0x0c)))
#define GPIO0_13             (*((volatile unsigned char *)(GPIO_BASE + 0x0d)))
#define GPIO0_14             (*((volatile unsigned char *)(GPIO_BASE + 0x0e)))
#define GPIO0_15             (*((volatile unsigned char *)(GPIO_BASE + 0x0f)))
#define GPIO0_16             (*((volatile unsigned char *)(GPIO_BASE + 0x10)))
#define GPIO0_17             (*((volatile unsigned char *)(GPIO_BASE + 0x11)))
#define GPIO_DIR             (*((volatile unsigned int *)(GPIO_BASE + 0x2000)))
#define GPIO_MASK            (*((volatile unsigned int *)(GPIO_BASE + 0x2080)))
#define GPIO_PORT            (*((volatile unsigned int *)(GPIO_BASE + 0x2100)))
#define GPIO_MPORT           (*((volatile unsigned int *)(GPIO_BASE + 0x2180)))
#define GPIO_SET             (*((volatile unsigned int *)(GPIO_BASE + 0x2200)))
#define GPIO_CLR             (*((volatile unsigned int *)(GPIO_BASE + 0x2280)))
#define GPIO_NOT             (*((volatile unsigned int *)(GPIO_BASE + 0x2300)))

// Switch Matrix
#define SM_BASE              0x4000C000
#define PINASSIGN0           (*((volatile unsigned int *)(SM_BASE + 0x000)))
#define PINASSIGN1           (*((volatile unsigned int *)(SM_BASE + 0x004)))
#define PINASSIGN2           (*((volatile unsigned int *)(SM_BASE + 0x008)))
#define PINASSIGN3           (*((volatile unsigned int *)(SM_BASE + 0x00c)))
#define PINASSIGN4           (*((volatile unsigned int *)(SM_BASE + 0x010)))
#define PINASSIGN5           (*((volatile unsigned int *)(SM_BASE + 0x014)))
#define PINASSIGN6           (*((volatile unsigned int *)(SM_BASE + 0x018)))
#define PINASSIGN7           (*((volatile unsigned int *)(SM_BASE + 0x01c)))
#define PINASSIGN8           (*((volatile unsigned int *)(SM_BASE + 0x020)))
#define PINENABLE0           (*((volatile unsigned int *)(SM_BASE + 0x1c0)))

// USART0
#define USART0_BASE	     0x40064000
#define USART0_CFG           (*((volatile unsigned int *)(USART0_BASE + 0x00)))
#define USART0_CTL           (*((volatile unsigned int *)(USART0_BASE + 0x04)))
#define USART0_STAT          (*((volatile unsigned int *)(USART0_BASE + 0x08)))
#define USART0_INTENSET      (*((volatile unsigned int *)(USART0_BASE + 0x0c)))
#define USART0_INTENCLR      (*((volatile unsigned int *)(USART0_BASE + 0x10)))
#define USART0_RXDAT         (*((volatile unsigned int *)(USART0_BASE + 0x14)))
#define USART0_RXDATSTAT     (*((volatile unsigned int *)(USART0_BASE + 0x18)))
#define USART0_TXDAT         (*((volatile unsigned int *)(USART0_BASE + 0x1c)))
#define USART0_BRG           (*((volatile unsigned int *)(USART0_BASE + 0x20)))
#define USART0_INTSTAT       (*((volatile unsigned int *)(USART0_BASE + 0x24)))

// USART1
#define USART1_BASE	     0x40068000
#define USART1_CFG           (*((volatile unsigned int *)(USART1_BASE + 0x00)))
#define USART1_CTL           (*((volatile unsigned int *)(USART1_BASE + 0x04)))
#define USART1_STAT          (*((volatile unsigned int *)(USART1_BASE + 0x08)))
#define USART1_INTENSET      (*((volatile unsigned int *)(USART1_BASE + 0x0c)))
#define USART1_INTENCLR      (*((volatile unsigned int *)(USART1_BASE + 0x10)))
#define USART1_RXDAT         (*((volatile unsigned int *)(USART1_BASE + 0x14)))
#define USART1_RXDATSTAT     (*((volatile unsigned int *)(USART1_BASE + 0x18)))
#define USART1_TXDAT         (*((volatile unsigned int *)(USART1_BASE + 0x1c)))
#define USART1_BRG           (*((volatile unsigned int *)(USART1_BASE + 0x20)))
#define USART1_INTSTAT       (*((volatile unsigned int *)(USART1_BASE + 0x24)))

// USART2
#define USART2_BASE			     0x4006C000
#define USART2_CFG           (*((volatile unsigned int *)(USART2_BASE + 0x00)))
#define USART2_CTL           (*((volatile unsigned int *)(USART2_BASE + 0x04)))
#define USART2_STAT          (*((volatile unsigned int *)(USART2_BASE + 0x08)))
#define USART2_INTENSET      (*((volatile unsigned int *)(USART2_BASE + 0x0c)))
#define USART2_INTENCLR      (*((volatile unsigned int *)(USART2_BASE + 0x10)))
#define USART2_RXDAT	     (*((volatile unsigned int *)(USART2_BASE + 0x14)))
#define USART2_RXDATSTA      (*((volatile unsigned int *)(USART2_BASE + 0x18)))
#define USART2_TXDAT         (*((volatile unsigned int *)(USART2_BASE + 0x1c)))
#define USART2_BRG           (*((volatile unsigned int *)(USART2_BASE + 0x20)))
#define USART2_INTSTAT       (*((volatile unsigned int *)(USART2_BASE + 0x24)))

// SYSCON
#define SYSCON_BASE          0x40048000
#define SYSCON_MAP           (*((volatile unsigned int *)(SYSCON_BASE + 0x000)))
#define SYSCON_PRESETCTRL    (*((volatile unsigned int *)(SYSCON_BASE + 0x004)))
#define SYSCON_SYSPLLCTRL    (*((volatile unsigned int *)(SYSCON_BASE + 0x008)))
#define SYSCON_SYSPLLSTAT    (*((volatile unsigned int *)(SYSCON_BASE + 0x00c)))
#define SYSCON_SYSOSCCTRL    (*((volatile unsigned int *)(SYSCON_BASE + 0x010)))
#define SYSCON_WDTOSCCTRL    (*((volatile unsigned int *)(SYSCON_BASE + 0x024)))
#define SYSCON_SYSRSTSTAT    (*((volatile unsigned int *)(SYSCON_BASE + 0x030)))
#define SYSCON_SYSPLLCLKSEL  (*((volatile unsigned int *)(SYSCON_BASE + 0x040)))
#define SYSCON_SYSPLLCLKUEN  (*((volatile unsigned int *)(SYSCON_BASE + 0x044)))
#define SYSCON_MAINCLKSEL    (*((volatile unsigned int *)(SYSCON_BASE + 0x070)))
#define SYSCON_MAINCLKUEN    (*((volatile unsigned int *)(SYSCON_BASE + 0x074)))
#define SYSCON_SYSAHBCLKDIV  (*((volatile unsigned int *)(SYSCON_BASE + 0x078)))
#define SYSCON_SYSAHBCLKCTRL (*((volatile unsigned int *)(SYSCON_BASE + 0x080)))
#define SYSCON_UARTCLKDIV    (*((volatile unsigned int *)(SYSCON_BASE + 0x094)))
#define SYSCON_CLKOUTSEL     (*((volatile unsigned int *)(SYSCON_BASE + 0x0e0)))
#define SYSCON_CLKOUTUEN     (*((volatile unsigned int *)(SYSCON_BASE + 0x0e4)))
#define SYSCON_CLKOUTDIV     (*((volatile unsigned int *)(SYSCON_BASE + 0x0e8)))
#define SYSCON_UARTFRGDIV    (*((volatile unsigned int *)(SYSCON_BASE + 0x0f0)))
#define SYSCON_UARTFRGMULT   (*((volatile unsigned int *)(SYSCON_BASE + 0x0f4)))
#define SYSCON_EXTTRACECMD   (*((volatile unsigned int *)(SYSCON_BASE + 0x0fc)))
#define SYSCON_PIOPORCAP0    (*((volatile unsigned int *)(SYSCON_BASE + 0x100)))
#define SYSCON_IOCONCLKDIV6  (*((volatile unsigned int *)(SYSCON_BASE + 0x134)))
#define SYSCON_IOCONCLKDIV5  (*((volatile unsigned int *)(SYSCON_BASE + 0x138)))
#define SYSCON_IOCONCLKDIV4  (*((volatile unsigned int *)(SYSCON_BASE + 0x13c)))
#define SYSCON_IOCONCLKDIV3  (*((volatile unsigned int *)(SYSCON_BASE + 0x140)))
#define SYSCON_IOCONCLKDIV2  (*((volatile unsigned int *)(SYSCON_BASE + 0x144)))
#define SYSCON_IOCONCLKDIV1  (*((volatile unsigned int *)(SYSCON_BASE + 0x148)))
#define SYSCON_IOCONCLKDIV0  (*((volatile unsigned int *)(SYSCON_BASE + 0x14c)))
#define SYSCON_BODCTRL       (*((volatile unsigned int *)(SYSCON_BASE + 0x150)))
#define SYSCON_SYSTCKCAL     (*((volatile unsigned int *)(SYSCON_BASE + 0x154)))
#define SYSCON_IRQLATENCY    (*((volatile unsigned int *)(SYSCON_BASE + 0x170)))
#define SYSCON_NMISRC        (*((volatile unsigned int *)(SYSCON_BASE + 0x174)))
#define SYSCON_PINTSEL0      (*((volatile unsigned int *)(SYSCON_BASE + 0x178)))
#define SYSCON_PINTSEL1      (*((volatile unsigned int *)(SYSCON_BASE + 0x17c)))
#define SYSCON_PINTSEL2      (*((volatile unsigned int *)(SYSCON_BASE + 0x180)))
#define SYSCON_PINTSEL3      (*((volatile unsigned int *)(SYSCON_BASE + 0x184)))
#define SYSCON_PINTSEL4      (*((volatile unsigned int *)(SYSCON_BASE + 0x188)))
#define SYSCON_PINTSEL5      (*((volatile unsigned int *)(SYSCON_BASE + 0x18c)))
#define SYSCON_PINTSEL6      (*((volatile unsigned int *)(SYSCON_BASE + 0x190)))
#define SYSCON_PINTSEL7      (*((volatile unsigned int *)(SYSCON_BASE + 0x194)))
#define SYSCON_STARTERP0     (*((volatile unsigned int *)(SYSCON_BASE + 0x204)))
#define SYSCON_STARTERP1     (*((volatile unsigned int *)(SYSCON_BASE + 0x214)))
#define SYSCON_PDSLEEPCFG    (*((volatile unsigned int *)(SYSCON_BASE + 0x230)))
#define SYSCON_PDAWAKECFG    (*((volatile unsigned int *)(SYSCON_BASE + 0x234)))
#define SYSCON_PDRUNCFG      (*((volatile unsigned int *)(SYSCON_BASE + 0x238)))
#define SYSCON_DEVICE_ID     (*((volatile unsigned int *)(SYSCON_BASE + 0x3f8)))

// NVIC 
#define SYS_BASE             0xE000E000
#define NVIC_ST_CTRL         (*((volatile unsigned int *)(SYS_BASE + 0x010)))
#define NVIC_ST_RELOAD       (*((volatile unsigned int *)(SYS_BASE + 0x014))) 
#define NVIC_ST_CURRENT      (*((volatile unsigned int *)(SYS_BASE + 0x018))) 
#define NVIC_ST_CAL          (*((volatile unsigned int *)(SYS_BASE + 0x01C)))
#define NVIC_ISER0           (*((volatile unsigned int *)(SYS_BASE + 0x100)))
#define NVIC_ICER0           (*((volatile unsigned int *)(SYS_BASE + 0x180)))
#define NVIC_ISPR0           (*((volatile unsigned int *)(SYS_BASE + 0x200)))
#define NVIC_ICPR0           (*((volatile unsigned int *)(SYS_BASE + 0x280)))
#define NVIC_IABR0           (*((volatile unsigned int *)(SYS_BASE + 0x300)))
#define NVIC_IPR0            (*((volatile unsigned int *)(SYS_BASE + 0x400)))
#define NVIC_IPR1            (*((volatile unsigned int *)(SYS_BASE + 0x404)))
#define NVIC_IPR2            (*((volatile unsigned int *)(SYS_BASE + 0x408)))
#define NVIC_IPR3            (*((volatile unsigned int *)(SYS_BASE + 0x40c)))
#define NVIC_IPR6            (*((volatile unsigned int *)(SYS_BASE + 0x418)))
#define NVIC_IPR7            (*((volatile unsigned int *)(SYS_BASE + 0x41c)))
#define NVIC_ST_CTRL_CLK_SRC 0x00000004  //Clock Source
#define NVIC_ST_CTRL_ENABLE  0x00000001  //Enable
#define SCB_SHP0             (*((volatile unsigned int *)(0xE000ED1C)))
#define SCB_SHP1             (*((volatile unsigned int *)(0xE000ED20)))

//System Tick Configuration
//Initializes the System Timer and its interrupt, and starts the System Tick Timer.
//Counter is in free running mode to generate periodic interrupts.
//ticks = Number of ticks between two interrupts.
static inline uint32_t SysTickConfig(uint32_t ticks) {
  if (ticks > 0xFFFFFFUL)
    return (1); //Reload value impossible
  //set reload register
  NVIC_ST_RELOAD = (ticks&0xFFFFFFUL) - 1;
  //set Priority for Systick Interrupt
  SCB_SHP1 = 0xC0000000;
  //Load the SysTick Counter
  NVIC_ST_CURRENT = 0;
  //Enable SysTick IRQ and SysTick Timer 
  NVIC_ST_CTRL = 0x07;
  return (0);
}

//enable SysTick
void SysTickEnable(void) {
  NVIC_ST_CTRL |= NVIC_ST_CTRL_CLK_SRC | NVIC_ST_CTRL_ENABLE;
}

//disable SysTick
void SysTickDisable(void) {
  NVIC_ST_CTRL &= ~(NVIC_ST_CTRL_ENABLE);
}

void SysTickReset(void) {
	NVIC_ST_CURRENT = 0; //clear
}

void GpioInit(void) {
  //enable AHB clock to the GPIO domain
  SYSCON_SYSAHBCLKCTRL |= 0x40;
  //peripheral reset control to gpio/gpio int
  SYSCON_PRESETCTRL &= ~(0x400);
  SYSCON_PRESETCTRL |= 0x400;
}

//get port pin digital value
uint32_t GpioGetPin(uint32_t pinbit) {
  uint32_t result = 0;
  
  if (pinbit < 0x20)
    if (GPIO_PORT&(1<<pinbit))
      result = 1;
    else if (pinbit == 0xff)
      result = GPIO_PORT;
  return(result);
}

//System clock to the IOCON & the SWM need to be enabled or
//most of the I/O related peripherals won't work.
void SystemInit(void) {
  SYSCON_SYSAHBCLKCTRL |= 0x40080;
}

#define CLKOUTCLK_SRC_IRC_OSC       0
#define CLKOUTCLK_SRC_SYS_OSC       1
#define CLKOUTCLK_SRC_WDT_OSC       2
#define CLKOUTCLK_SRC_MAIN_CLK      3

//Configure CLKOUT for reference clock check.
//parameters: (clock source) irc_osc(0), sys_osc(1), wdt_osc(2), main_clk(3).			 
void ClockSetup(uint32_t clksrc) {
  //debug PLL after configuration
  SYSCON_CLKOUTSEL = clksrc;        //select main clock
  SYSCON_CLKOUTUEN = 0x01;          //update clock
  while (!(SYSCON_CLKOUTUEN&0x01)); //wait until update complete
  SYSCON_CLKOUTDIV = 1;             //divide by 1
  return;
}

Programming The Chip
ftdi cable
Programming the µC is simple. I used the free Flash Magic program, a SparkFun FTDI cable and followed the instructions posted here.

To put the board into ISP (programming) mode, simply hold the ISP button down while pressing/releasing the RESET button, and then release the ISP button. Once programmed, simply press the RESET button to run the uploaded code.

Be careful, the bare µC can be damaged if powered at greater than 3.3V. Typically FTDI cables with 3.3V logic levels, output 5V on the VCC line. However, the board used here incorporates a 3.3V voltage regulator, so this cable works fine. Also remember to swap the TX/RX lines between the cable and the board.

A Very Unscientific Code-Size Comparison
While it’s impossible to make direct comparison of code size between 8 and 32-bit compilers and µCs, I can report the following about my LED Blink program vs. the Arduino Basic Blink demo (ihex file size, release builds, with compiler optimization turned on):

  • IAR code size: 1,008 bytes.
  • LPCxpresso code size: 1,331 bytes.
  • Ardiuno (1.6.0) ATMega328 code size: 1,030 bytes.

My next post about ARM Cortex-M0.

More to follow…

Advertisements

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s