EmbeddedRelated.com
Blogs

C++ on microcontrollers 2 - LPCXpresso, LPC-link, Code Sourcery, lpc21isp, linkerscript, LPC1114 startup

Wouter van OoijenOctober 24, 20115 comments

 previous parts: 1

This blog series is about the use of C++ for modern microcontrollers. My plan is to show the gradual development of a basic I/O library. I will introduce the object-oriented C++ features that are used step by step, to provide a gentle yet practical introduction into C++ for C programmers.  Reader input is very much appreciated, you might even steer me in the direction you find most interesting.

I teach my students that code will be read much more often than it is written, but the ultimate fate of code is of course to be run. Showing the evolution of a library without giving you the chance to play with it is a bit dull. So I selected a development environment that you can use to run my examples and do your own experiments.

For the hardware, there is an easy choice: the LPCXpresso boards. For E 20 (plus shipping) Embedded Artists sells a compact board that contains an LPC-link debugger/loader with USB interface, and a target chip (you have a choice of various LPC1xxx chips). One USB cable powers the board and communicates with use the debugger/loader. I bought three versions (with LPC1114, LPC1343 and LPC1769 target chips) but to prove that C++ can be used on a small microcontroller I’ll start with the smallest one: the LPC114, which contains 32 kB FLASH and 8 kB RAM. The schematics of the board are available from the Embedded Artists website.

For E 82 more (E 89 when bought separately) Embedded Artists sells a base board with a wide range of peripherals. For some mysterious reason the circuit diagram and other detailed documentation of this board is only available after registration with Embedded Artists. In order to register you need the serial number of your base board, so you can’t do that before you have purchased the base board. Hey guys, you got wonderful products, why do I get the impression that you don’t want them to be used? But it is a nice board, so I’ll use it.

The LPCXpresso LPC1114 board consists of two subboards, which can be separated and used separately, or if needed, be re-connected with a pin and pin header strip. (That is the theory. I tried, but after separating the boards and soldering connectors, the LPC-link would no longer connect to the target chip. Maybe I should have cut the board all the way through instead of breaking it after making a shallow cut.) The LPC-link part is a USB to JTAG interface. It contains an LPC3154 with a USB bootloader. This bootloader accepts only signed executables, so you won’t be able to use it except for what NXP, Embedded Artists and Code Red want you to run on it: a size-limited JTAG interface, locked to the development environment they supply. You can use it that way if you want, but I don’t like big fat IDE’s, and particularly I don’t being locked into one, so I won’t go that route.

If you want, you can use the LPC-link as a standalone downloader (instead of as a debugger). I would have chosen that option, except that I could not figure out how the target chip could write debug logging information to the PC. This feature is available in the IDE, but as far as I could find out not without it. To use the LPC-link as a downloader, you must download (for which you have to register…), install and register the Code Red development environment. The procedure for using the LPC-link from the command line can be found at here. Basically, when the LPC-link is first plugged into your PC you must run a script to download its firmware. After that you can use a command line tool it to download and run an application. The command line depends on your windows version. Before I decided against this route I made an lpc_link_run.bat file that does the grunt work. You can find it on my website.

One snag is that an erroneous application can mess up the target’s clock, which disables its JTAG interface. (Symptom: error message “Et: flash driver not ready”) But you need the JATG to put in new code! The way out of this situation is to power the chip up into bootloader mode. For the LPC1114 this is done by grounding pin PIO0_1, which is marked FT/GPIO on the LPC1114 board. To be on the safe side, I use a 1k resistor, so I am less likely to damage the board when I connect the wrong pins.

The LPC1114 contains on on-chip bootloader, which is activated when the chip finds pin PIO0_1 low during startup. The LPCXpresso base board has an FT232 USB-to-serial interface chip that connects to the serial interface of the LPC1114, and to the reset and boot-mode pins of the chip. This makes it possible to download and run an application, and communicate with it from your PC. To use this you must install the USB driver for the chip before you connect the board to your PC. Note that the USB connector is the small one between the grey plastic connection block and a jumper block, not the one next to the Ethernet and USB-B connectors. (That one connects to the target chip’s USB interface, which our LPC1114 does not have.) Your PC will create a virtual com port. For convenience, I always rename it to COM4. To do this open the device manager, select the newly created COM port, select properties, port settings, advanced, and change the COM port number. Windows might complain that the port you want to change it to is in use, but you can ignore this unless it is a hardware serial port (does anyone still have those?), or is otherwise in use at that moment, in which case you must decide on another port number for LPC downloading.

If you don’t want to use the base board (maybe you even want to use a bare LPC1114 chip) you can rig up your own bootloader interface, either from a serial port and a max232, or from an FT232 chip. Check this datasheet, page 9, for a max232 based circuit. It is for an LPC2106, but apart from the pin numbers it is the same for an LPC1114.

To connect the LPC114 board to the base board you must solder two rows of pin headers to it (provided with the base board). Alternatively, you could solder two rows of wire cups to it (not provided), so you can use it with a solderless breadboard. I did both, which is a bit tedious to do. In retrospect I should have ordered two LPC1114 boards and use one with the base board and the other with the solderless breadboard.

To download an application to the LPC1114 (via the bootloader) you need a PC application. This list shows some of the options.  Flash Magic seems to be the official tool for this, but it is a GUI and even as a GUI I don’t like it. My favorite is lpc21isp, an open-source command line tool. For the LPC1114 a recent version is needed. I found only the source, but it compiled without a glitch with DevCpp. You can find the executable on my website. The lpc21isp command to download an application, run it, and open a simple two-way serial communication to it is (using port COM4 at 19k2):

lpc21isp –control –term application.hex COM4 19200 12000

For most LPC chips the last argument is the crystal frequency in kHz. As far as I could find out the LPC114 bootloader uses the 12 MHz RC oscillator that is internal to the LPC1114 chip. The argument must still be present, but it seems otherwise to be ignored.

The next step is a compiler tool chain. My preference is Code Sourcery, the ‘official’ maintainers of the ARM target of the GCC, now bought by Mentor Graphics. Their toolchain will likely contain the latest improvemens, even before they make it into the main GCC distribution. Their free GCC download is now called Sourcery CodeBench Lite. Select the EABI version (meaning: for a target without an operating system).

Next on the wish list is a sample project that blinks a LED. For this we need a linkerscript, the chip header files, an application program, and a makefile that calls all the tools with the correct parameters. I’ll explain the linkerscript and startup each in some detail (the real files, especially the linkerscript, are more complex). On my website you can find a .zip that contains these files.

The linkerscript specifies where things are to be placed in memory. It starts with the specification of the memory available in the target. In our case, 32kB ROM starting at address 0, and 8K RAM starting at address 0x10000000. Next is a long list that specifies where each section, produced by the compiler, is to be placed.

MEMORY {

   rom (rx)     : org = 0x00000000, len = 32k

   ram (rwx)    : org = 0x10000000, len = 8k

}

SECTIONS {

   . . .

}

The .bss section contains the global variables and objects in your program that do not have an initial value. This section is of course placed in RAM. The C language requires that the memory for this global data is initialized to zero. The two lines that look like assignments provide to the startup code the begin and end addresses of the .bss section, so it can write 0’s to it.

   .bss : {

      __bss_start = .;

      *(.bss .bss.* .gnu.linkonce.b.*)

      __bss_end = .;

   } > ram

 The .text section contains all parts of the application that are read-only: the machine instructions and the read-only data. It is placed in Flash. By default the linker will include only those parts that are needed (referenced) by other parts. This reduces the size of your application by removing unused (dead) code and variables. The KEEP lines specify that those parts are to be included even when they are not referenced. KEEP is used for the Cortex startup vectors and the C++ global object constructors.

  .text : {

     KEEP(*(.vectors));

 

     *(.rodata .rodata.* .gnu.linkonce.r.*);

 

     __init_array_start = .;

     KEEP(*(.init_array));

     __init_array_end = .;

 

   } > rom

 The vectors part is the very first part mentioned, hence it is placed at the start of the Flash, at address 0. When the LPC1114 starts execution, it loads the Stack Pointer and Program Counter from addresses 0 and 4 (each 32-bit word takes 4 addresses). Hence by specifying what is in the vectors part, we can control how the LPC1114 starts execution.

The init_array sections contain the addresses of the constructors of all global objects. The startup code must call each of these constructors before it calls main(). The assignment-like lines provide the startup code the begin and end of the init_array.

The .data section contains the global data in your application that has an initial value (other than zero), but must be writeable. (Data that is initialized but read-only can be placed in the .text section, although not all compilers seem to do this.) As far as the application code is concerned this data must be located in RAM, otherwise it would not be writeable. But the initial values must be placed in ROM, and the startup code must copy this ROM-image of the .data section to its RAM-image. The ”> ram AT > rom” line causes each .data section to be placed at two locations: the initial values are put in ROM, while all references made to the variables by the application code will point to the RAM. Again, the assignment-like lines provide to the startup code the begin of the ROM-image, and the begin and end of the RAM-image of the .data section.

   .data : {

      __data_init_start = LOADADDR (.data);

      __data_start = .;

      *(.data .data.* .gnu.linkonce.d.*)

      __data_end = .;

   } > ram AT > rom

 The final section is the stack. No parts produced by the compiler are put in this section, it simply occupies the number of bytes required by __stack_size, which was declared at the start of the linkerscript. The __stack_end is used by the startup code as initial value for the Stack Pointer. (The stack grows downwards, so its initial value is the end of the stack.)

   __stack_size = 1024;

    .stack : {

      . += __stack_size;

       __stack_end = .;

   } > ram

 The startup code for the LPC1114 chip must arrange that it gets control when the chip starts executing. This is easily achieved by placing an array of two words in the vectors section. The first word contains the initial value for the Stack Pointer, the second word the initial value for the Program Counter.  The __attribute__  is the GNU-specific syntax that instructs the compiler to place this declaration in a .vectors section. Otherwise it would have ended up in a .data section, where it would not serve any purpose. The __stack_end must be declared as an external variable, so the linker can provide its address to the C code. The type of this variable is not important. The __startup is a forward declaration for the startup function which we will discuss next.

void __startup( void );

extern int __stack_end;             

void * vectors[ 16 ] __attribute__((section(".vectors"))) = {

      & __stack_end,

        __startup                         

};

Note that the vectors[] array is declared to have 16 entries. At first I declared it to have just the two entries that I thought were needed: the stack pointer and the program counter. This seemed to work, until I enabled optimization. Then it sometimes worked, but adding a single statement somewhere (even it was never executed) could make it crash. In a flash of inspiration I remembered that the LPC bootloader requires that certain vector entry contains the checksum of the other vectors. The toolchain automatically adds this checksum at the appropriate address. When the vectors array has only two entries this checksum entry ends up in the middle of the startup code, which (sometimes!) fatal effects.

The vectors array has arranged for a valid stack pointer and a PC that starts executing the __startup function. This function first clears (writes zero) to the RAM part that is occupied by the .bss section. It gets the begin and end (actually, end + 1) of the section from the linker, and a simple while loop clears the memory. The linkerscript aligns the start of each section on a 4 byte boundary (I omitted such details), hence an integer pointer can be used to do the clearing.

void __startup(void){

   extern int __data_init_start;

   extern int __data_start;

   extern int __data_end;

   extern int __bss_start;

   extern int __bss_end;

   int *s, *d, *e;

 

   // clear .bss section

   d = & __bss_start;

   e = & __bss_end;

   while( d != e ){

      *d++ = 0;

   }

Next the global read-write data that has an initial value is copied from its ROM-image to the place it occupies in RAM.

   // copy .data section from flash to ram

   s = & __data_init_start;

   d = & __data_start;

   e = & __data_end;

   while( d != e ){

      *d++ = *s++;

   }

Next the constructors of all global objects are called. Note the syntax required to cast to a pointer to a function pointer, dereference it, and call the function. You can show that to anyone who things C declaration syntax is well designed!

   // call the constructorts of global objects

   s = & __init_array_start;

   e = & __init_array_end;

   while( s != e ){

      (*((void(**)())s++))();

   }      

With that action everything that the C++ language requires to be in place is present, so we can call main(). In an embedded system main() should never return (there is no Operating System to return to). When it nevertheless does, we enter an endless loop: better lock up the processor than going on doing something unpredictable.

   // call main

   (void) main();

  

   // when main returns: loop till hell freezes down

   while(1){}

}   

With the linkerscript and startup in place, we need the actual application code. At startup the LPC1114 chip will run from its internal 12 MHz RC clock. We don’t need optimal speed or timing accuracy yet, so we can leave it at that.

To use one of the IO pins to blink a LED we need an LPC1114-specific output_pin class. The LPC1114 user manual shows that each IO port has a memory region associated with it. These regions start at 0x50000000 for port 0, plus 0x10000 for each next port. To simplify the code I wrote a simple function that returns a pointer to an address at the specified offset within the region that belongs to the specified point. 

volatile int *gpioreg( unsigned int port, unsigned int offset = 0 ){

   return (volatile int *)( 0x50000000 + port * 0x10000 + offset );

}

The GPIO direction register is located at offset 0x800 within the points data region. It has one bit for each I/O pin, which must be set to make the pin an output.  That is easily done, suing the gpioreg() function.

*gpioreg( port, 0x8000 ) |= 1 << pin;       

The data region associated with each port has a range of addresses that can be used to write to a subset of the pins. The offset (from the start of the region) is the set of pins that is to be written, shifted left two positions. We want to write to just one pin, so the address is formed by taking the pin number, adding 2, and shifting 1 left that number of times. Check for yourself that this code does the same.

write_pointer = gpioreg( port, 0x04 << pin );    

That’s all the machinery we need for our LPC1xxx-specific out_pin class. The gpioreg() function is put in the private part of the class because it is no one’s business. It is marked ”static” because it does not need any pin_lpc1 data: it is associated with the class, rather than with a specific object.

Now take a look at the whole class. The constructor makes the pin an output, and initializes the write_pointer, which is a private data field of the class.  The write method just writes to the location pointed to by the write_pointer. When writing a 0, the value written must have a 0 in the bit position corresponding to the pin. That’s easy, the value 0 has 0’s in all bit positions. When a 1 is to be written we use -1, which (C++ requires 2’s complement representation for negative numbers) has 1’s in all positions.

class pin_lpc1: public output_pin {

private:

   volatile int *write_pointer;

  

   static volatile int *gpioreg(

      unsigned int port, unsigned int offfset = 0

   ){

      return (volatile int *)

         ( 0x50000000 + port * 0x10000 + offset );

   }

  

public:

   pin_lpc1( unsigned int port, unsigned int pin ){

    *gpioreg( port, 0x8000 ) |= 1 << pin;

       write_pointer = gpioreg( port, 0x04 << pin );        

   }

  

   void set( bool x ){

      *write_pointer = x ? -1 : 0;        

   }

};

To get a blinking effect we need a delay. Basic as it seems, a delay function can be the most problematic thing to get working on a new chip. Demo code often uses a simple loop, but the timing of such a loop is very compiler dependent, and any optimization setting above ‘no optimization’ might eliminate the loop altogether. I use the Cortex SysTick timer, which is available in every Cortex chip. This counter counts up to a specified reload value, at the CPU frequency divided by 2. The function first stops the timer. Next the reload value is set to 6 times the wait time in microseconds: The CPU frequency is 12 MHz, divided by 2 yields 6 MHz.  Next the timer is cleared and started, and we wait until the timeout bit is set. By the way: sorry for the magic number ‘6’, I’ll come up with a better version in a later blog. A fixed number won’t do anyway when we allow the CPU frequency to be changed.

Note that for the pin class I calculated the register addresses from scratch. For the wait() function I have used the LPC11xx.h file provided by NXP.

#include "LPC11xx.h"

 

void wait( unsigned int t ){

   SysTick->CTRL = 0;      // stop the timer

   SysTick->LOAD = t * 6;  // CPU clock / ( 2 * 6 ) => 1 MHz

   SysTick->VAL = 0;       // clear the timer

   SysTick->CTRL = 1;      // start the timer

   while( ( SysTick->CTRL & ( 1 << 16 ) ) == 0){} // wait for timeout

}

This does work, but only when the ( t * 6 ) expression does not overflow the 24 bit of the timer. 2^24 / 6 microseconds ~= 3 seconds, so overflow might occur for reasonable delays. A simple way to protect the wait() function is to let it loop on itself until the remaining wait time is low enough.

void wait( unsigned int t ){

   while( t > 1000 ){

      wait( 1000 ); 

      t -= 1000;

   }

   SysTick->CTRL = 0;

   SysTick->LOAD     = t * 6;

   SysTick->VAL = 0;

   SysTick->CTRL = 1;

   while( ( SysTick->CTRL & ( 1 << 16 ) ) == 0){}

}

I have changed the good old blink() function to use the new wait() function. Note that the very fact that I had to modify it hints that there is some problem here. More on that later.

void blink( output_pin &led, unsigned int delay = 200 * 1000 ){

    for(;;){

       led.set( 1 );

       wait(  delay );

       led.set( 0 );

       wait(  delay );

   }     

};

Finally we can instantiate an LPC1114 object and call blink() with pin 7 to get a blinking LED on our LPCXpresso LPC1114 PCB.

lpc1114 target;                     

int main( void ){         

     blink( target.pio0_7 );

}

On my website you can find an archive with all project files. The Makefile supports the ‘run’ target, which builds the application and downloads it by lpc21isp using COM4. My favourite command line is ‘make clean run’ which somehow sounds reassuring :) .



[ - ]
Comment by jj8431October 24, 2019

Great job, well done. I just did the similar thing at about the same time (in 2010). 

Just for your information that how small a C++ code can get. That project was a commercial project with TI's MSP430F2312, which used virtual functions as well. The project used almost every resources on chip, it uses self-flash-programming, key pad, beeper, LED indicator, 3 timers, SPI, UART and A/D ,and all of these were packed in a chip that has only 4KB of FLASH.

[ - ]
Comment by hjvanderlindenNovember 13, 2011
Hi Wouter, Thanks very much for the great tutorials you are writing. Keep up the good work! Heiko
[ - ]
Comment by wovoNovember 15, 2011
Thanks. You are welcome to suggest subjects that you would like to see discussed.
[ - ]
Comment by lipnyMarch 29, 2012
I can't make the LPClink work at all. My PC does not seems to see it. And I can't find your lpc_link_run.bat either. I have this installed: LPCXpresso: Version: LPCXpresso v4.2.0 [Build 264]
[ - ]
Comment by wovoMarch 29, 2012
Thanks, I have repaired the links.

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: