Bare-metal mbed; KLBasic is up and running!
(Last updated 23 Jan 2012)

23 Jan 2012 -- You can now download hex files for external code modules, such as support libraries or standalone programs, into KLB via the console.  This means you can write dedicated modules in C or assembly, download them into KLBasic, then run them from the interpreter or from a stored Basic program on autostart.  See below for details on using the hex download feature.  See below for details on writing custom external control modules (ECMs).



I now have my KLBasic modified to run on the mbed (LPC1768) device.  KLBasic is a tokenized interpreter built upon work done earlier by Gordon Doughman of Motorola for the 68hc11.  Gordon released the code (in 68hc11 assembler) and I recoded it in C so it is (mostly) platform independent.  For more info on KLB in general, go back to my main page and check out the link for KLBasic on the Atmel devices.

In the mbed implementation, KLB is a 32-bit integer Basic that reduces your source lines into tokens, then runs the tokenized program through an interpreter.  The source is fully tokenized, including any whitespace.  When you list out your program, the KLB runtime simply re-expands the tokenized file.  Your original source is not stored as ASCII text; it is always rederived from this list of tokens.

Here is a list of KLBasic features that are derived from Gordon's original design:
Here is a list of additional features that I've added to KLBasic:
Here is a list of features on my to-do list:
KLBasic is intended for controller-type embedded applications.  Think home automation, greenhouse control, alarm systems, temperature control, model railroading, hobby racing monitors, that kind of thing.



Known issues
The per-line internal tokenizing logic is not robust; it is possible for the tokenizer to walk off the edge of the token buffer.  I need to fix the logic, but for now the buffer will hold 128 tokens, which should be plenty for typical source lines.



Some examples
Since examples are always the best way to show off stuff, here is a KLBasic program for blinking the mbed LEDs:

 100 ' Program to blink a few LEDs
 110 '
 200 dim leds(4)
 210 dim states(4)
 220 dim timers(4)
 300 leds(1) = 2^18
 310 states(1) = 0
 320 timers(1) = addr(timer0)
 350 leds(2) = 2^20
 360 states(2) = 0
 370 timers(2) = addr(timer1)
 400 leds(3) = 2^21
 410 states(3) = 0
 420 timers(3) = addr(timer2)
 450 leds(4) = 2^23
 460 states(4) = 0
 470 timers(4) = addr(timer3)
 800 while 1 = 1
 820   for n = 1 to 4
 840     if @32 timers(n) = 0 then gosub 2000
 860   next n
 880 endwh
 2000 '
 2010 ' Subroutine to change an LED state and rearm the
 2020 ' LED timer.  Variable n holds LED index.
 2030 '
 2060 if states(n) = 0 then gpio1_set = leds(n)
 2080 if states(n) = 1 then gpio1_clr = leds(n)
 2100 states(n) = 1 - states(n)
 2110 p = timers(n)
 2120 @32 p = rnd(2000) + 500
 2190 return

Lines 200 - 220 create arrays for holding information on the LEDs.  Each LED is assigned a state (1 or 0), a timer (timer0 through timer3), and a mask for setting/clearing the LED output bit.  The masks all correspond to bits associated with LED1 through LED4 on the mbed board.  Line 300, for example, creates a mask for LED1 by setting bit 18 in the array element leds(1).

Lines 300 - 470 fill the arrays with information about the LEDs.  In particular, lines 320, 370, 420, and 470 each write the address of a timer into an array element.  These elements will later serve as pointers to a down-counting timer.

Lines 800 - 880 are the main loop.  This loop cycles through all four LEDs, checking to see if the associated timer has reached 0.  When a down-counting timer hits 0, it stops decrementing, which means the original delay has elapsed.

Line 840 shows how to use the 32-bit indirection operator to access the value pointed to by the contents of timers(n).  The @32 operator tells KLB that the value in timers(n) is not the value to test, but a pointer to the value to test.  Similar operators exist for 8-bit and 16-bit pointers.

Lines 2000 - 2190 are the subroutine that changes the state of an LED and rearms the associated timer with a random number.  Line 2120 shows how to use the @32 operator in the assignment side of an expression.  Again, variable P is used as a pointer to the value to be changed.  Since P holds the address of a down-counting timer, line 2120 assigns a random number to that timer, not to P.

To load this program, hook the mbed's UART0 up to a terminal program (38400, 8N1).  Type in the above program.  Type in the command RUN to see the show.  (KLBasic commands and variable names are case-insensitive; foo, FOO, and FoO are all the same).

To save this program to a flash file, type in the command SAVE FL0.  This will write the program to flash file 0; there are a total of three flash files (fl0 through fl2).

To load a program from a flash file, type in the command LOAD FLn, where n is 0 - 2.  After the above save, you would reload the program by entering LOAD FL0.

To run the program automatically on reset, type in the command AUTOST FL0.  On the next reset, the mbed will copy the program from flash file fl0 to RAM and begin execution.

KLBasic is an interpreter, so it isn't going to match the speed of compiled C.  However, it runs fast enough to do a lot of general-purpose programs.  For an example of its speed as an interpreter, the following program:

 100 timer0 = 1000
 110 n = 0
 200 while timer0 <> 0
 210   n = n + 1
 300 endwh
 400 ?n

reports a value of N of 20970, which indicates 21K loops per second.

Having a Basic interpreter on-board comes in really handy when you are adding hardware to your mbed.  For example, the following program will monitor the value of AD0 in real-time, so you can tweak your voltage settings or whatever:

 100 while 1 = 1
 110   timer0 = 250 : while timer0 > 0 : endwh
 120   print chr$(13); (ad0 / 16) and $fff;
 140 endwh

This program is an endless loop.  To end the program after it starts running, just enter Ctrl-C in your terminal program.

KLBasic does not support QuickBasic's EXPLICIT command.  If you type a name that might be construed as a variable, that variable is created for you.  This can lead to problems if you make a mistake when typing a variable's name; you will get two different variables with similar names.  To help catch these situations, KLBasic supports variants of the LIST command.  Here are some examples, using the blinky program above:

>list 2000

 2000 '

>list 2000 -

 2000 '
 2010 ' Subroutine to change an LED state and rearm the
 2020 ' LED timer.  Variable n holds LED index.
 2030 '
 2060 if states(n) = 0 then gpio1_set = leds(n)
 2080 if states(n) = 1 then gpio1_clr = leds(n)
 2100 states(n) = 1 - states(n)
 2110 p = timers(n)
 2120 @32 p = rnd(2000) + 500
 2190 return

>list vars

List of variables --
leds()              states()            timers()            n
p

>list ports

List of ports --
timer0        timer1        timer2        timer3        pconp
pclksel0      pclksel1      pinsel10      pinsel0       pinsel1
pinsel2       pinsel3       pinsel4       pinsel5       pinsel6
pinsel7       pinsel8       pinsel9       pinmode0      pinmode1
pinmode2      pinmode3      pinmode4      pinmode5      pinmode6
pinmode7      pinmode8      pinmode9      pwm1ir        pwm1tcr
pwm1tc        pwm1tpr       pwm1tpc       pwm1mcr       pwm1mr0
pwm1mr1       pwm1mr2       pwm1mr3       pwm1mr4       pwm1mr5
pwm1mr6       pwm1ccr       pwm1cr0       pwm1cr1       pwm1cr2
pwm1cr3       pwm1pcr       pwm1ler       pwm1ctcr      gpio0_dir
gpio1_dir     gpio2_dir     gpio3_dir     gpio0_set     gpio1_set
gpio2_set     gpio3_set     gpio0_clr     gpio1_clr     gpio2_clr
gpio3_clr     gpio0_pin     gpio1_pin     gpio2_pin     gpio3_pin
adcr          adgdr         adstat        ad0           ad1
ad2           ad3           ad4           ad5           ad6
ad7           uptime        breakflag     vectortable



As you can see, the list of ports known to KLBasic is pretty limited right now.  That will change in the future as I expand the device table.

The UPTIME port is not really an LPC1768 port.  It is the number of milliseconds that the mbed has been running since the latest reset.  This value is updated each tic in the background.

The BREAKFLAG port is a U8 variable that is FALSE until the user presses ctrl-C to break a program during execution.

The VECTORTABLE port is the address of the RAM vector table used by KLBasic.  The original vectors at the start of flash are copied to this address before KLBasic starts up.  Other programs, either KLB itself or an ECM, can modify the contents of this vector table and thus take control of selected interrupts.  Obviously, use this carefully; if you autosave a program that corrupts the vector table, you will have to reload KLBasic to recover.

KLBasic is an interactive program.  If you suddenly have to know what is 3 raised to a random power between 4 and 8, type: ? 3^(4+rnd(4))

KLBasic does not provide a WYSIWYG editor.  Instead, it uses the original line-number based model.  If you need to change a line, reenter that line.  This becomes less of a hassle with a good terminal program.  TeraTerm, for example, has an excellent copy-and-paste facility that makes it easy to edit code and to save full programs to a local text file for later retransmission to the mbed.



Using KLBasic on your mbed
You need to have a serial connection between the mbed's UART0 and your PC.  This is usually done through the USB port.  For Windows boxes, open your terminal program setup menu and select the COM port associated with your mbed's USB connection.  Set the terminal program on the PC to 38400, 8N1.

The LPC1768 does not have any byte-addressable on-chip non-volatile storage, such as EEPROM.  It does, however, have several battery-backed registers in the RTC subsystem, so I borrowed one of those to hold the non-volatile autostart flag.  This means that if you want to use the autostart feature, you will have to connect a battery, typically a CR2032 or other small 3V lithium cell, to your mbed device.  Hook the positive terminal of the battery to pin 3 (VB) on the mbed, and hook the negative terminal of the battery to pin 1 (GND).  You can run KLBasic without having the battery connected, but you won't be able to autostart a program following power-cycle.

Note that installing a binary file in the mbed always erases the original flash contents.  This means that if you save some KLB programs to flash, then decide to load a new binary onto your mbed, you will lose all of your saved flash files.  I have plans to add support for a serial EEPROM for saving files, but for now just be aware of this issue.

Note that there is currently no mechanism for defeating the autostart outside of KLBasic.  This means that if you have tagged a program for autostart and there is a serious flaw in that program, you will have to use a Ctrl-C from a terminal in order to break the program after it starts, then turn off the autostart feature.  This is not usually a problem during development, since you usually have the mbed hooked to a terminal.

Flash files are saved to upper flash, one 32 KB sector per file.  FL0 is written to sector 24 (0x50000), FL1 is written to sector 25 (0x58000), and FL2 is written to sector 26 (0x60000).

To install KLBasic, just drop the basicmbed.bin file into the folder of your mbed device on your computer's desktop.  KLBasic is written in ANSI C and weighs in at about 55 KB (version 0.2).

Once installed, hook up your terminal program (I use TeraTerm Pro but any good term program should work) and reset your mbed.  You should see the KLBasic signon:


KLBasic for mbed (LPC1768) v0.2
KLBasic core v0.7
Core written by Karl Lunt, based on Gordon Doughman's BASIC11
READY
>


You can download the mbed version of KLBasic here.

 KLBasic is a work in progress.  I have really enjoyed getting this running on a 100 MHz mbed.  I will continue adding features and fixing bugs, to make this program even better.  Please drop me an email if you have comments or features you would like to see.


Using hex files with KLBasic
KLB lets you download hex files from the console and store them in flash sector 28 (0x70000 - 0x77fff).  Because this address range is covered by a single sector, each time you download a hex file the previous contents of that sector are erased and lost.

The hex download must be started from the command prompt; it cannot be started from within a KLB program.

Here is a typical download sequence:

KLBasic for mbed (LPC1768) v0.2
KLBasic core v0.7
Core written by Karl Lunt, based on Gordon Doughman's BASIC11
READY
>load hex
Ready to begin loading hex file from console.
Hex file must exist entirely in sector 28 ($70000 to $77FFF).
If you need to abort the hex load, type Cntrl-C on console.
Begin transfer now...
:0200000270008C
:1000000004E0000000F034B800F046B82DE91F000D
:100010004FF000000C490D4A914206D0A2F10102B6
:100020009142086001F10401FAD3094809490A4ADA
:10003000002A05D010F8014B01F8014B013AF9D123
:100040001FBC00F00BB800000000082004000820CE
:100050008C010700000008200000000080B483B07D
:1000600000AF7860396007F10C07BD4680BC70476F
:1000700080B483B000AF786039603B68012B04D155
:100080007B681B681A46034B1A6007F10C07BD46D4
:1000900080BC70470000082080B487B000AF786053
:1000A0003960384B1B68002B64D0374B4FF4340257
:1000B0001A60354B4FF43402DA613B68032B5BD195
:1000C0007B681B683B614FF000037B617B6803F139
:1000D00004031B6803F00103DBB2002B03D07B6930
:1000E00043F480237B617B6803F104031B6803F006
:1000F0000203002B03D07B6943F480137B617B6890
:1001000003F104031B6803F00403002B03D07B6995
:1001100043F400137B617B6803F104031B6803F065
:100120000803002B03D07B6943F400037B617B68E9
:1001300003F108031B68FB6000E000BF124B7A6903
:100140009A613B69FA681A6000BF3B691B68002B23
:10015000FBD10D4B7A69DA613B69FA681A6000BF1E
:100160003B691B68002BFBD1064B1B681B78002BDF
:10017000E3D002E000BF00E000BF07F11C07BD466E
:0C01800080BC70470000082020C009204F
:040000037000000089
:00000001FF
Wrote total of 396 bytes.

>

After the 'load hex' command, KLB prompts you to start the file load.  At this point, use your terminal program's ability to send a file as ASCII text to send the selected .hex file.  I usually use a delay at the end of each line of 25 msecs to get reliable transfers, YMMV.

The above download leaves the hex file installed in flash, starting at 0x70000.  The program will remain until overwritten, either by another hex load or by reflashing the device through another USB download..

You can use the CALL statement to execute a program in this flash area.  For example, assume your program has a starting address of 0x70000.  To invoke this program, enter:

call $70000

Notice that KLB uses '$' instead of '0x' to denote hexadecimal constants.



Using External Code Modules (ECM)s
"External Code Module" means an executable that was installed on the mbed external to KLBasic, usually through a hex load operation from the console.  Creating such a module is straightforward and can be done in C or assembly language, or any other language that supports the GCC C-routine model.

An ECM consists of these elements: One or more C source files comprising the executable, a custom startup file (usually assembly), a custom linker script, and a makefile.

The following example blinks the four LEDs on the mbed, and is about the most convoluted way you could blink LEDs.  :-)

First up is the C source file:

#include  <stdio.h>

#include  "LPC17xx.h"


static  uint8_t                *breakflag;


/*
 *  Initialize      low-level module initialization
 *
 *  Note that you MUST call this routine before invoking any other
 *  elements of the module!  Even though there is no code in this
 *  function, calling this routine (the first entry in the module's
 *  jump table) forces the initialization of the module's .data sections.
 *  If you don't invoke this function, variables won't be zeroed or
 *  initialized!
 */
void  Initialize(uint32_t  *args, uint32_t  nargs)
{
}


/*
 *  GetBreakflag      register a pointer to the breakflag
 *
 *  This routine takes a single argument.  args[0] must hold
 *  a pointer to the KLBasic break flag.  This module will
 *  later test the contents of this break flag to determine
 *  if the user has entered a ctrl-C at the console to halt
 *  the program.
 */
void  GetBreakflag(uint32_t  *args, uint32_t  nargs)
{
    if (nargs == 1)  breakflag = (uint8_t *)args[0];
}



/*
 *  Blinky      blink an LED the convoluted way
 *
 *  Upon entry, nargs must be 3.
 *  args[0] must hold the address of a down-counting timer
 *  for use by this routine.
 *  args[1] must hold a mask of LEDs to blink; bit 0 set
 *  means blink LED1, etc.
 *  args[2] holds the delay, in msecs, for each LED state
 *  change.
 *
 *  This routine does not return until the user hits a ctrl-C
 *  on the console.
 *
 *  If you didn't invoke GetBreakflag() to register the address
 *  of the break flag, this routine exits immediately.
 */
void  Blinky(uint32_t  *args, uint32_t  nargs)
{
    uint32_t                    delay;
    uint32_t                    mask;
    uint32_t                    *timer;

/*
 *  If the breakflag pointer is empty, the user has no way
 *  to break out, short of a reset.  Let's be nice and just
 *  refuse to run until the breakflag pointer is at least
 *  not NULL.
 */
    if (breakflag == 0)  return;

/*
 *  Turn off all mbed LEDs so we know we got this far.
 */
    LPC_GPIO1->FIODIR = ((1<<18) | (1<<20) | (1<<21) | (1<<23));
    LPC_GPIO1->FIOCLR = ((1<<18) | (1<<20) | (1<<21) | (1<<23));
    if (nargs != 3)  return;

/*
 *  Record the timer we get to use.
 */
    timer = (uint32_t *)args[0];

/*
 *  Translate the mask arg into a mask suitable for controlling
 *  the mbed LEDs.
 */
    mask = 0;
    if (args[1] & (1<<0))  mask = mask | (1<<18);
    if (args[1] & (1<<1))  mask = mask | (1<<20);
    if (args[1] & (1<<2))  mask = mask | (1<<21);
    if (args[1] & (1<<3))  mask = mask | (1<<23);

/*
 *  Record the delay.
 */
    delay = args[2];

/*
 *  Start the loop that blinks the LEDs.  Use the timer
 *  to pace the LED changes.
 */
    while (1)
    {
        LPC_GPIO1->FIOSET = mask;
        *timer = delay;
        while (*timer)  ;
        LPC_GPIO1->FIOCLR = mask;
        *timer = delay;
        while (*timer)  ;

/*
 *  If the break flag is not zero, the user wants to quit
 *  the blinky program.
 */
        if (*breakflag)  return;
    }
}


This file provides three routines.  Initialize() actually does nothing.  It exists as a jump target for the startup module (see below).  I could have collapsed all three of these routines into a single function, but left them this way so you can see the mechanics of how ECMs are accessed by KLBasic.

The GetBreakflag() routine gives KLB a hook to pass in the address of the KLBasic breakflag variable.  This is a system variable that is normally FALSE but will go TRUE if the user enters a ctrl-C from the console.  Passing the address of this variable to the ECM allows a function in the ECM to test the value of the breakflag and take action if the user wants to break out.

The Blinky() routine accepts three arguments when invoked.  The first argument is the address of one of the four KLBasic down-counting timers.  Blinky will use this timer to pace the blink-rate of the LEDs.  The second argument is a mask (bits 0-3) that define the LEDs to blink.  The third argument is the amount of time (in msecs) between state changes on the LEDs.

Notice how arguments are passed into each of these functions when invoked.  All three functions, and all functions in any ECM, are defined to accept two arguments.  Much like the traditional definition of main(), these functions expect a pointer to an array of 32-bit values (args) and the number of elements in the args[] array (nargs).  The actual contents of the args array is up to you; you control how the args list is processed in your ECM function.

Next up is the ECM startup file.  Here is the startup file for the blinky ECM:

/*
 *  ECM_startup.s        generic startup file for use with ECMs
 *                      (External Code Modules)
 *
 *  This is a generic LPC17xx startup script, suitable for use with
 *  the CodeSourcery Lite gcc toolset.
 *
 *  However, this startup code is not intended to act as a true
 *  starting point following a cold boot.  Instead, this code is
 *  intended to act as gateway to funtions that can be invoked
 *  by other programs.  These function can be invoked by
 *  executing jumps into a collection of jump vectors at the
 *  start of this module.
 *
 *  This code is based on several examples I found on the web, mashed
 *  together to do what I want it to do.  My thanks to the various
 *  authors for their contributions.
 */

    .syntax unified
    .thumb

    .section ".jump_vector_table"
    .global __jump_vector_table

/*
 *  Unlike a traditional startup file, this file is NOT intended to
 *  occupy the vector table area, and in fact has no traditional
 *  vectors or stack pointer initialization value.
 *
 *  Note that ECMs usually use the stack pointer that is passed to them
 *  when a function is invoked from outside; there isn't really any need for
 *  a stack for ECMs.  However, if you decide you want to have an ECM-
 *  specific stack, use a linker script that includes support for it.  Note that
 *  your ECM module will have to modify the stack pointer itself; there
 *  are no provisions for setting up the stack as part of an ECM's make.
 */

/*
 *  This jump vector table provides target jump points for other
 *  programs to "call" modules within this project.
 *
 *  For every entry in this jump vector table, you must provide an
 *  identically named C function in your ECM module.  You do not need
 *  to have a function named main(), though you are free to use that
 *  name if you like.
 */
    .balign 4
__jump_vector_table:
    b    _Private_Initialize        @ special case, need to set up C vars first!
    .balign 4                    @ important!  this forces a 4-byte entry for the first label
    b    GetBreakflag
    b    Blinky


/*
 *  Actual code.
 */
    .thumb_func
    .global _Private_Initialize    @ make it visible in the map file
    .global GetBreakflag
    .global Blinky

/*
 *  Control jumps to here when the first entry in the jump table
 *  is invoked.  Unlike other jump table entries, the first entry
 *  performs the housekeeping functions normally done by the C
 *  startup code following reset.
 *
 *  If your calling program does not first invoke this vector,
 *  your variables will NOT be initialized!
 */
 
_Private_Initialize:
    stmdb sp!, {r0-r4}        @ save the critical regs
/*
 *  Clear the BSS section
 */
    mov r0, #0
    ldr r1, = _start_bss
    ldr r2, = _end_bss
    cmp    r1, r2
    beq    _done_clear

    sub r2, #1
_clear:
    cmp r1, r2
    str r0, [r1, #0]
    add r1, #4
    blo _clear
_done_clear:


/*
 *  Copy data from flash initialization area to RAM
 *
 *  The three values seen here are supplied by the linker script
 */
    ldr   r0, =_start_data_flash    /* initial values, found in flash */
    ldr   r1, =_start_data            /* target locations in RAM to write */
    ldr   r2, =_data_size            /* number of bytes to write */

/*
 *  Perform the copy.
 *  Handle the special case where _data_size == 0
 */
    cmp   r2, #0
    beq   done_copy
copy:
    ldrb   r4, [r0], #1
    strb   r4, [r1], #1
    subs   r2, r2, #1
    bne    copy
done_copy:

/*
 *  Done with copy, restore critical regs and jump to initializer in C.
 */
    ldmia sp!, {r0-r4}
    b        Initialize
   
.end



At first glance, this looks very much like a traditional bare-metal startup file.  However, the vector table at the beginning of the file has been modified slightly to act as a jump-vector table, not an interrupt vector table.  In this case, the only entries in the table are pointers to the functions within your ECM that you want to provide access to in KLBasic.  Here, the second and third entries provide access to GetBreakflag() and Blinky(), respectively.

The first entry in the vector table, however, is different.  It is a short jump to initialization code in the file, which in turn initializes variables used by the ECM.  Note that only the first entry in the table provides this initialization.  Therefore, it is important that when you use this ECM, you invoke the first vector before invoking any others.  Otherwise, the other functions will not have their variables initialized and will surely fail.

After the first vector finishes initializing the ECM's variables, it passes control to the function named in the branch at the bottom of the RAM copy loop (below label done_copy), which in this case means invoking function Initialize().  In the example C file above, the function Initialize() didn't do anything, but it serves as a jump target for this first vector.  Once invoked, Initialize() simply returns back to KLBasic.

The next piece of the puzzle is the linker script.  Here is the script for the blinky ECM:


/* Adapted for CortexM3 LPC1768, originally based on LPC21xx and LPC22xx User
 * Manual UM10144, pg. 15. */

OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)


/*
 *  The ECM module uses a small section of flash in the upper range of the
 *  mbed's memory space.
 *
 *  The ECM module uses the static RAM reserved for APB1 peripherals (16K).
 */
MEMORY
{
    flash (rx)  : ORIGIN = 0x00070000, LENGTH = 32K
    sram (rwx) : ORIGIN = 0x20080000, LENGTH =  16K
}

/*
 *  Define the top our stack at the end of SRAM
 *
 *  Note that ECMs usually use the stack pointer that is passed to them
 *  when a function is invoked from outside; there isn't really any need for
 *  a stack for ECMs.  However, if you decide you want to have an ECM-
 *  specific stack, this linker script includes support for it.  Note that
 *  your ECM program will have to modify the stack pointer itself; there
 *  are no provisions for setting up the stack as part of an ECM's make.
 */
TOTAL_RESERVED_STACK = 8196;        /* note that printf() and other stdio routines use 4K+ from stack! */
_end_stack = 0x20084000;

EXTERN(__jump_vector_table);

SECTIONS
{
    .text :
    {
        CREATE_OBJECT_SYMBOLS
        /* Insert the jump vector table first */
        __jump_vector_table = .;
        *(.jump_vector_table)

        /* Rest of the code (C) */
        *(.text)
        *(.text.*)
        *(.glue_7)
        *(.glue_7t)

/* Added following section for holding initializers for variables   */
/* found in RAM.
/*
  The _data_size value will be used in the startup code to step through
  the image of data in flash and copy it to RAM.
*/
        . = ALIGN(4);
/*        _start_data_flash = .;  */
        *(.rodata)
        *(.rodata*)
        *(.init)                    /* added */
        *(.fini)                    /* added */
        . = ALIGN(4);
        _end_data_flash = .;
    } >flash

  /*  From generic.ld, supplied by CodeSourcery  */
  /* .ARM.exidx is sorted, so has to go in its own output section.  */
    PROVIDE_HIDDEN (__exidx_start = .);
    .ARM.exidx :
    {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } >sram 
    PROVIDE_HIDDEN (__exidx_end = .);

   
/*    .data : AT (_end_data_flash)   */
    .data :
    {
        _start_data_flash = LOADADDR(.data);
        _start_data = .;
        *(.data)
        *(.data.*)
        *(.shdata)
        _end_data = .;
    } >sram  AT>flash
    . = ALIGN(4);
    _data_size = _end_data - _start_data;

    .noinit :
    {
        *(.noinit)
        *(.noinit.*)
    }
   
    _start_bss = .;
    .bss :
    {
        *(.bss)
        *(.bss.*)
        *(COMMON)
    } >sram
    . = ALIGN(4);
    _end_bss = .;

    bss_size = _end_bss - _start_bss;


    /* Stack can grow down to here, right after data and bss sections in
     * SRAM */
    _start_stack = _end_stack - TOTAL_RESERVED_STACK;
    _end_stack = _end_stack;            /* just to make the map file easier to read */

    /* Linker wants .eh_frame section defined because of gcc 4.4.X bug,
     * just discard it here. */
    /DISCARD/ :
    {
        *(.eh_*)
    }
}

_end = .;
PROVIDE(end = .);



This linker script is pretty simple.  It defines the memory available for the ECM, ensures that the jump vector table is placed at the beginning of available flash, then locates the rest of the program's sections.

The combination of the linker script and the startup program above results in a block of jump vectors, each four bytes long, that starts at address $70000.  How many vectors you provide depends solely on the needs of your ECM, though you must provide at least the first vector to ensure your ECM's variables are initialized.

To build the ECM, run the associated makefile, shown here:

#
#  Makefile for ECM_blinky.o
#
#  You can run this makefile from the command line with:
#
#  cs-make -f ECM_blinky.mak           or
#  cs-make -f ECM_blinky.mak clean
#
#  Make sure the CodeSourcery cs-make.exe is in your
#  execution path.
#

#  Project Name
PROJECT = ECM_blinky

#  List of the objects files to be compiled/assembled
OBJECTS = $(PROJECT).o

#  This project does not create an executable, so it does
#  not use a linker script.
LSCRIPT =

#  List of directories to be included during compilation
INCDIRS = ..\include

#  List of additional object modules to link in.
#  Use this variable to point to prebuilt object modules
#  that exist outside of a library (such as the startup
#  code).
ADDOBJS =

OPTIMIZATION = 0
DEBUG = -g
ASLISTING = -alhs
LIBDIRS =
LIBS =


#  Compiler Options
GCFLAGS = -Wall -fno-common -mcpu=cortex-m3 -mthumb -O$(OPTIMIZATION) $(DEBUG)
GCFLAGS += -I$(INCDIRS)
#GCFLAGS += -Wcast-align -Wcast-qual -Wimplicit -Wpointer-arith -Wswitch
#GCFLAGS += -Wredundant-decls -Wreturn-type -Wshadow -Wunused
LDFLAGS = -mcpu=cortex-m3 -mthumb -O$(OPTIMIZATION) -nostartfiles -Wl,-Map=$(PROJECT).map -T$(LSCRIPT)
LDFLAGS += -L$(LIBDIRS)
LDFLAGS += -l$(LIBS)

ASFLAGS = $(ASLISTING) -mcpu=cortex-m3

#  Compiler/Assembler/Linker Paths
GCC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
REMOVE = rm -f
SIZE = arm-none-eabi-size

#########################################################################

all:: $(PROJECT).o

clean:
    $(REMOVE) $(OBJECTS)
    $(REMOVE) *.lst

#########################################################################
#  Default rules to compile .c and .cpp file to .o
#  and assemble .s files to .o

.c.o :
    $(GCC) $(GCFLAGS) -c $<

.cpp.o :
    $(GCC) $(GCFLAGS) -c $<

.s.o :
    $(AS) $(ASFLAGS) -o $@ $< > $(basename $@).lst

#########################################################################



When this makefile completes, you will be left with a file named ECM_blinky.hex, which you can download into the mbed using KLBasic's 'load hex' command.

Invoking the functions in this ECM consists of three calls into the vector table.  Here is a KLB program for running this convoluted blinky program:

 100 call $70000
 120 call $70004, addr(breakflag)
 140 call $70008, addr(timer0), 10, 250

Line 100 initializes the ECM variables, line 120 passes in the address of the breakflag, and line 140 defines the associated timer, the mask, and the delay, then starts blinking the LEDs.  Inside KLBasic, the code for the CALL statement places the arguments (maximum of four) into an args[] array, then passes the address of this array and the number of elements in it (nargs) to the target vector.

To halt the program and return to KLBasic's prompt, hit ctrl-C on the terminal's keyboard.

Here is a .zip archive of the full ECM_blinky project.



Home