Riscy Pygness User Manual

Riscy Pygness is Pygmy Forth for the ARM.

This is a preliminary version of the manual. If you notice any errors or omissions or if it leaves you with unanswered questions, please email me your notes and questions.

Introduction
Installation Checklists
Minimal installation
Install ARM binutils
Install Emacs and forthblocks mode
Supported Processors
LPCxxxx
other ARM CPUs
Documentation for individual program files
makefile
riscy.tcl
riscy.asm
custom include files
Source code
Blocks
Text files
The Forth model
Brief description
Additional notes
Internals and Design
Support Software
Binutils (assembler, etc.)
Gdb or Insight
Tcl
Flash Utilities (downloaders)
Make
Terminal
Creating a new kernel image
Multitasking
Creating a task
Stopping and starting a task
Limitations and cautions
Switching back to interactive mode with ;;
Interactive strings
C,
Constants and LOAD
Appendix
Reader prerequisites
Board notes
Tools summary
FAQ
JTAG and OpenOCD
GDB
Emacs
Antecedents
colorForth
GNU Toolchain
Makefile automatic variables
Troubleshooting
Contact Information

Introduction

Riscy Pygness is a 32-bit multitasking Pygmy Forth for the ARM. It includes full source code for both the host (your desktop PC) and target (your ARM development board). The license is BSD/MIT-like so you can do (nearly) anything you like with it.

It is aimed at relatively small embedded ARM systems rather than desktop ARM systems or large embedded ARM systems running an operating system (OS). Riscy Pygness is a stand-alone system that is its own multitasking OS. The current version needs about 4 KB of flash and 1.5 KB RAM. (The size can be reduced further depending on your needs.) This makes it suitable for use in even the smaller ARM variants such as these Philips NXP

LPC2101 8 KB flash 2 KB RAM
LPC2102 16 KB flash 4 KB RAM
LPC2103 32 KB flash 8 KB RAM

It can address the full 4 GB address space, so it can take advantage of all the flash and RAM available in the larger variants.

During development, the host communicates with the target via a serial port. The host provides the smart terminal and the compiling services. The host can generate a new, customized Forth image for the target.

The Forth itself runs on the target but you interact with it by typing commands on the host, much as you would with a Forth running locally on the host.

As you type each line of Forth words (commands) at the terminal, the line is compiled on the host then transmitted to the target either to be executed immediately (when "interpreting") or to extend the dictionary on the target (when "compiling"). (Yes, in either case, it is compiled on the host.) Numbers typed at the terminal are sent to the target to be put on the target's data stack. Word headers are kept on the host, not the target, and all compilation work is done on the host.

The host and the target, by working together this way, provide the effect of a fully interactive Forth running on the target while conserving the limited resources of the target.

You need a certain amount of background knowledge about computers, Linux, the command line, etc. in order to read this manual. If you do not yet have (all of) this knowledge, don't despair; it is easy to acquire. Start with the Reader Prerequisites section of the Appendix. If you are still lost, it might be a failure of this manual, so please email me with details about what you find confusing so I will have a chance to improve the manual.

Getting started using an existing image

Here is a quick overview. Also see the Checklists.

The Riscy Pygness distribution comes with several pre-built, ready-to-run kernel images. If you have an ARM board that matches one of these images, burn the *.bin file into the ARM's flash, then run with the matching *.dictionary file.

For example, if you have the Olimex LPC-P2106 board (http://olimex.com/dev/lpc-p1.html) (also available from Spark Fun http://www.sparkfun.com/commerce/product_info.php?products_id=269), look for the files

- kernel-lpc2106.bin
this is the binary image to be burned into the LPC2106's flash

Burn kernel-lpc2106.bin into the LPC2106. You can use whatever method you are already familiar with, or you can use the lpc21isp program. See the Flash utilities section for examples.

This step needs to be done just once (unless/until you generate a new kernel).

- kernel-lpc2106.dictionary
this is the matching dictionary file

Run Riscy Pygness via the riscy.tcl program, passing it the name of the dictionary file and, optionally, the name of your serial port.

    $ ./riscy.tcl -image kernel-lpc2106  -port /dev/ttyS0
   

Of course, substitute the correct serial port name if you are not using /dev/ttyS0.

The above command line works if Tcl is installed where the riscy.tcl script expects it (i.e., #!/usr/local/bin/tclkit). If you install Tcl elsewhere (e.g., /usr/bin/wish8.4), be sure to edit the first line of riscy.tcl to specify the correct path to Tcl.

That is a fairly long command line to type. You might prefer to set up a shell script or shell alias to give you a much shorter command to type. I use a script named r which is short for "run" to type the command for me — the distribution includes this example script. So, I start Riscy Pygness interactively by typing

    $ ./r
   

(be sure to edit it for your environment before using it). Or, create a shell alias with a command such as

    $ alias r='./riscy.tcl -image kernel-lpc2106 -port /dev/ttyS0'
   

and put it in your ~/.bashrc file. Then, you can start Riscy Pygness by typing

    $ r
   

Installation Checklists

This section provides detailed checklists for installing Riscy Pygness.

The checklists often include example commands to type. Most of these are typed in a terminal as an ordinary user, but some must be typed as root. The examples usually precede the command with $ to indicate an ordinary user or with # to indicate the root user. These example commands are often not the only way to do it. Please review each command before typing it to verify it makes sense for your environment.

Minimal installation

These steps are done just once. Of course, you need an ARM board with power supply and serial cable, and a desktop PC (preferably running Linux).

With the above, you can run the pre-built kernels and you can extend the dictionary interactively (into RAM). However, you cannot build a new kernel unless you install the ARM binutils as described in the next subsection.

Why would you wish to create a new kernel? There are serveral reasons:

Install ARM binutils

If you wish to build a custom kernel, you need to install the ARM binutils package. This will provide the assembler, linker, and object file manipulation utilities, with names such as arm-elf-as, arm-elf-ld, arm-elf-objdump.

You can compile the ARM binutils yourself. It is very easy. See the GNU Toolchain section for details. Note the value of the BIN variable in makefile. It must point to the directory where the ARM binutils programs reside. Currently, makefile expects them to be in /usr/local/bin. If you put them elsewhere, be sure to edit the BIN variable in makefile.

Alternatively, you could install a pre-compiled version of ARM binutils. (Do not simply run apt-get install binutils because that would install the package for your desktop CPU, but we need the version for the ARM.) You might be able to do apt-get install binutils-arm-linux-gnu (as root) if you are running Ubuntu.

Also, copy preasm.tcl to /usr/local/bin.

At this point, you have everything required to be able to generate new Riscy Pygness kernels.

Install Emacs and forthblocks mode

Of course, you can edit your source code files with any text editor. If you have a favorite, perhaps you are a vi enthusiast, feel free to continue to use it. Otherwise you can install Emacs with

# apt-get install emacs

There are some advantages to using the Emacs editor, along with the forthblocks.el extension (included in the Riscy Pygness zip file). Forthblocks mode allows you to work with plain text Forth code as if (almost as if) you were working with block files. These are very flexible blocks in that they can be as large or small as you like. See the Emacs section. The beginning of the file forthblocks.el contains details as to how to use and install it.

Supported Processors

LPCxxxx

Riscy Pygness should be easy to port to any ARM processor. So far, I have run it on the Philips NXP LPC family of ARM processors. It currently runs on at least the LPC2106, LPC2103, LPC2294, and the LPC2378. The modifications necessary for other LPC processors should be minimal.

Riscy Pygness currently works on the following ARM CPU and board variants (so I believe).

We can expect it to run on the entire LPC family of chips with minor adjustments for clock speed and such on specific boards.

The Appendix includes some information for several variants.

other ARM CPUs

I have attempted to isolate the CPU-specific and/or development board-specific part of the code in separate files. See custom-lpc2106.asm, custom-lpc2103.asm, and custom-lpc23xx.asm in the distribution. The main assembly language file, riscy.asm, contains one conditional directive which includes the appropriate CPU-specific file based on a symbol defined near the top of riscy.asm. The makefile edits that symbol automatically to match the particular processor.

The file equates-lpc2xxx.asm holds register definitions that apply to all of the NXP LPC2000 family members. In addition, there are board-specific equates files: olimex-lpc2106-equates.asm, olimex-lpc2103-equates.asm, and olimex-lpc2378-equates.asm.

To port to a different processor, copy whichever of the above files are most similar to your processor, and modify them. The goal, of course, is to isolate the parts that change from the parts that do not change, thus not cluttering the main source code with tons of conditional directives.

Porting to other manufacturers' ARM chips is straightforward. The main task will be adjusting for differences in how I/O and peripherals are handled, setting the clock speed, noting which pin (if any) connects to an LED, etc.

Documentation for individual program files

This section holds documentation specific to various files in the distribution. For better understanding of a system program (such as make or objcopy) you can view the manual for that program with a command such as one of the following

or by googling (of course).

makefile

The work of building the various parts of Riscy Pygness is controlled by a makefile named makefile. The program make (as guided by the file makefile) takes care of the details.

You need to look over, and possibly change, several variables for your system.

See the section on makefile variables for the ones you may need to edit.

To assemble a file named led1-2294.asm, creating an Intel hex file suitable for downloading, type

$ make led1-2294.bin

Then, or instead, you could type

$ make led1-2294.dl

in order to assemble, if necessary, and then download using the lpc21isp downloader.

The general idea is to assemble riscy.asm to make the riscy.bin file that riscy.tcl will use to create the *.bin and *.dictionary files by typing

$ make

What make then does is slightly more complicated, in that it does not directly assemble riscy.asm. Instead, riscy.asm serves as a template from which board-specific versions are created automatically. It is these resulting files (such as riscy-lpc2294.asm) that are assembled. This lets the makefile create kernel images for all the supported ARM/board variants at one time. If, for some reason, you would like to limit the work to just a single board variant, you could use

$ make lpc2106

downloading to the flash

Then, burn the *.bin (e.g., kernel-lpc2103.bin) into the ARM's flash with

$ make kernel-lpc2103.dl

Note that some flash utilities (for example, the lpc21isp flash utility when given the -control option) will control the serial port's DTR and RTS lines in an attempt reset the ARM while holding the bootloader request line low. This works only if your ARM board supports it. For example, on the Olimex LPC-P2378 board, short the two jumpers ISP_E and RST_E if you wish this to work. On hardware that does not support this, you need to change jumpers and/or push reset buttons manually in order for the bootloading to work.

On the Olimex LPC-L2294 board, it is probably best to leave RST_E open and to manually short the ISP_E jumper and press the reset button when downloading a program. Then, open the ISP_E jumper and press the reset button once again so the newly downloaded program can run.

variables

Edit the following variables in makefile depending on the particular ARM chip, serial port, path to the assembler, etc. that apply to your environment.

The only ones you normally need to verify/edit are CCLK, PORT, and BIN.

- CCLK
the CPU clock speed of your board in KHz. This is used by both the lpc_prog and the lpc21isp flash utilities. Note that this is the external crystal speed when using the LPC2106 and some others but is always 14748 when using the LPC2378.
- PORT
the full path of your PC's serial port, such as /dev/ttyS0 or COM1: for the first serial port.
- BIN
the full path to the directory that contains the ARM assembler (arm-elf-as) and other binutils utilities such as arm-elf-objdump.
- DLBAUD
the serial rate you wish to use for downloading. This defaults to 38400. Other possible speeds are 115200, 57600, 19200, 9600, 4800, etc.
- PREASM
the full (or relative) path to the preprocessor that converts *.asm files into *.s files. Normally the executable file is preasm.tcl but you could write a replacement in Python or sed or whatever if you prefer. Its main purpose at the moment is to replace semicolons (my preferred comment character) with at-signs (the GNU ARM assembler's preferred comment character).
- LNKFLAGS
this includes the link script as well as flags.
- ASMFLAGS
flags passed to the assembler.
- LNKFLAGS
flags passed to the linker.
- ZIPFILES
a list of the files to be zip'd when I say make zip to create a local backup. You would not ordinarily need to do this.

riscy.tcl

riscy.tcl is a Tcl program that runs on the host. It implements the host side of Riscy Pygness.

See particularly the first two subsections below. You need to verify/edit the path to Tcl some variables in riscy.tcl to suit your environment.

path to Tcl

Edit the top line of riscy.tcl so it points to Tcl on your system. By default, its value is

#!/usr/local/bin/tclkit

Better yet, put Tcl on your system where the top line expects to find it, if not there already, by going to http://www.equi4.com/ and downloading and unzipping an appropriate Tclkit.

For example, for a 32-bit Linux system running on x86 hardware, you might use this version of Tclkit:

http://www.equi4.com/pub/tk/8.5.1/tclkit-linux-x86.gz

Download it. Then, as root, move it to /usr/local/bin/ then symlink it so the top line of riscy.tcl will not need to be changed, e.g.,

   # cd /usr/local/bin/
   # mv /from/wherever/you/downloaded/it/tclkit-linux-x86.gz .
   # gunzip tclkit-linux-x86.gz
   # ln -s tclkit-linux-x86 tclkit
 

starting the program

riscy.tcl program serves two purposes:

  1. Create a kernel image file suitable for burning into the ARM chip's flash. This step also creates a matching dictionary file. The command line for this would be something like

    $ ./riscy.tcl -flash 1

    but you would not run this manually. It would be done automatically by the makefile.

  2. Run Riscy Pygness interactively, using the dictionary file previously created, and the matching kernel image file previously burned into the ARM chip's flash. For example, if the kernel image and dictionary files are named kernel-lpc2106.bin and kernel-lpc2106.dictionary, your command line would be

    $ ./riscy.tcl -image kernel-lpc2106 -port /dev/ttyS0

    to run interactively (using the /dev/ttyS0 serial port). Of course, you would set up a shell script or alias to reduce your typing. See the shell scripts r and burn for examples.

See below for more information and/or run riscy.tcl file with no arguments to see a help message describing how to use it.

Variables

There are certain variables (and one procedure) in riscy.tcl that you should verify or change to suit your environment. All of these are located near the top of the file.

- isUnix
Set to 1 to run on Linux (or other Unixes). If you wish to try running this on a Microsoft OS, change its value to 0.
- ::debug
Leave this set to 0 for normal use. Changing it to 1 causes (way too many) debugging messages to be printed. This setting can also be changed interactively with the Forth word DEBUG.
- ::serialPort1
Set this to the name of the serial port you use to connect to the target ARM board. Under Linux, this is usually either /dev/ttyS0 or /dev/ttyS1. Instead of setting it here, you can can specify it with the -port command line option, which will override the setting inside the program.
- ::baudrate
Set this to the same baud rate that the ARM chip is set to use. For the pre-built kernels, this is 38400 bps.
- filenameFromBlockNumber
This procedure maps Forth block numbers to file names. Add additional ranges and file names or change the existing ranges. It is best not to change the first one, which maps the first thousand blocks (0 through 999) to the filename kernel.fth.

riscy.asm

There is nothing to customize in this file for normal use. Of course, if you wish to add or delete or modify the primitives, this is where to do it.

This file is not assembled directly. Instead, this serves as a template file. The makefile takes riscy.asm and modifies the following equate

.equ <BOARD>, 1

to replace "<BOARD>" with the appropriate symbol for a specific ARM/board variant. For example, when assembling the primitives for the Olimex LPC-P2106 board, makefile replaces "<BOARD>" with "lpc2106" to put this line

.equ lpc2106, 1

in a new file named riscy-lpc2106.asm. The file riscy-lpc2106.asm is the one assembled (after pre-processing it).

If you add a new ARM/board variant, you will need to pick a new symbol to represent that variant, then add it to makefile and also to riscy.asm.

custom include files

Rather than putting lots of .ifdef statements throughout riscy.asm to adjust for the various ARM/board variants, we factor the differences for each variant into a custom include file (with a name such as custom-lpc2106.asm or custom-lpc2294.asm). A single conditional directive in riscy.asm picks the appropriate include file.

Source code

Blocks

blocks for source code

Traditionally, Forth code was kept in "screens" or "blocks". Each was a fixed size of 1024 bytes (characters), presented to the user in an editor that showed 16 lines of 64 characters.

This size is very convenient for source code and encourages modularity and short definitions.

In Riscy Pygness, we fake true 1024-byte blocks by using a text file divided into logical blocks by special comment lines.

Each "block" starts with a special Forth comment in the first line. The left parenthesis must be in the first column, followed by a space and either the word "block" or the word "shadow", followed by a space and a decimal number (the block number). Additional text may follow, but the comment must end eventually with a right parenthesis.

When we LOAD a block, the host program searches for that marker to know where to extract the text to be interpreted.

The procedure filenameFromBlockNumber (near the top of riscy.tcl) maps block number ranges to file names. Edit it to add additional block files or to change the ranges.

Block numbers should be in numerical order, else searching might fail. However, "holes" (missing block numbers) are ok.

riscy.tcl reads the entire file into the host's memory, but only when the file changes.

Here are some examples of special comment lines that would mark the beginning of a block.

( block 0  )

( block 1   ------------------  load block)

( block 2  miscellaneous)

( shadow 2 )

( block 3  )

 

When you type a Forth command such as 22 LOAD, the procedure filenameFromBlockNumber finds that block 22 belongs to the file named kernel.fth. If this is the current file, then it has already been read into memory (on the host), otherwise it becomes the current file and is read into memory. Using the block comment lines, riscy.tcl finds the range of lines to load.

Using the forthblocks mode in Emacs, a single block is displayed at a time. PgDn (or C-v) moves to the following block. PgUp (or M-v) moves to the previous block. M-a ("a" for "alternate") toggles between a block and its corresponding shadow block. Traditionally, Forth code with short comments goes on the "block" and lengthier comments go on the "shadow".

What I usually do when I start a new Forth file is to write one comment line for a block and another for a shadow, e.g.,

( block 0 )
( shadow 0 )
 

Then copy and paste the two a bunch of times, producing something like

( block 0 )
( shadow 0 )
( block 0 )
( shadow 0 )
( block 0 )
( shadow 0 )
( block 0 )
( shadow 0 )
 

Then run the Emacs forthblocks mode command M-x renumber-blocks to fix up the numbers automatically, producing

( block 0 )
( shadow 0 )
( block 1 )
( shadow 1 )
( block 2 )
( shadow 2 )
( block 3 )
( shadow 3 )
 

See the file forthblocks.el for more details on how to set it up and use it.

blocks for data

Another traditional use of 1024-byte blocks is for data storage. This use is not particularly addressed by the text-file-based block system described in the previous subsection. Nothing precludes using certain block ranges for text-file-based blocks for source code storage, and using different block ranges for pure 1024-byte data blocks (located perhaps in RAM, flash, or SD cards).

Text files

Some people apparently really dislike using blocks for source code. If you are one of those people, you could try the hybrid approach discussed above (where you use plain text files for source except that special comments mark the logical blocks) to see if you grow to like it.

Otherwise, for the source for your application, use a plain text file but start it with a single comment such as

( block 2000 -- this entire file file is block 2000 )

then forget about blocks. The entire text file will be in that single block. 2000 LOAD will load the entire text file.

Note, do not do this with the file kernel.fth as that is used to generate the kernel.

The Forth model

Brief description

  1. split address space during development
  2. host side written in Tcl
  3. primitives are written in assembly language and assembled with the GNU ARM assembler.
  4. the host program runs in one of two modes;

Additional notes

      : COUNT-DOWN ( u -)  ?DUP IF DUP . 1-  COUNT-DOWN ; THEN ;
      

      : COUNT-DOWN ( u -)  BEGIN ?DUP WHILE DUP . 1- REPEAT ;
      

Internals and Design

Riscy Pygness is a member of the Pygmy Forth family but is quite different from the 16-bit DOS Pygmy Forth. It takes a number of ideas from Charles Moore's colorForth, yet uses conventional Forth-style notation, rather than using color.

There are many possible ways of threading the words within a higher-level word. The method in this version is token-threaded code with 16-bit tokens. This is a compromise between speed and conciseness that favors conciseness. By using 16-bit tokens, a lot of code can be packed into the available flash (and RAM). We could modify the threading to favor speed or even to favor greater conciseness. Generally, though, I lean toward the usual Forth philosophy of ignoring performance until the application is working and then convert just the bottlenecks to CODE words (i.e., Forth words written in assembly language).

The Forth image that is burned into flash consists of the primitives (which are written in assembly language and assembled by the GNU ARM assembler) and the high-level Forth (which is compiled by a Tcl program). The Tcl program takes the binary image and a list of symbols and addresses produced by the assembler and merges them into an in-memory image on the host, along with the higher-level Forth, then writes that image to a file named, for example, kernel-lpc2103.bin, ready to be burned into flash on the target. At the same time, it writes a dictionary file, such as kernel-lpc2103.dictionary that is used by the host to map Forth word names to their target addresses.

Typical development style is interactive, exercising the words in the dictionary and extending the dictionary on the fly to test and experiment. From time to time, the high-level Forth that will go into flash is extended, based on these interactive sessions, and a new kernel (flash image) is created. This cycle is repeated until the application is finished and all of it is in flash.

In other words, you start with a base image in flash and do most of your work interactively (with extensions going into RAM). Periodically, you generate a new base image to be burned into flash that includes your new work.

Support Software

For the assembler and gdb, see Installing the GNU Toolchain.

Binutils (assembler, etc.)

The GNU ARM toolchain supplies the assembler (and linker and debugger, etc.).

The full GNU ARM toolchain (with gcc, the C compiler) is not needed, just the binutils package, which is quite easy to install. Binutils supplies the assembler and linker and various object file tools. It can be installed as a binary package — there are a number of sites on the web supplying precompiled ARM toolchains, for both Linux and Microsoft Windows — or you can compile binutils yourself.

Note, the GNU tools can be given a prefix when you compile them so that there is no conflict between the ones for the ARM and the ones for your native Linux system. For example, I use "arm-elf-" as the prefix, so my ARM assembler is named arm-elf-as while my native x86 assembler is named simply as.

Also, note that the assembler is used only for the primitives. Some Forth implementations cram the high-level definitions into the assembly source in a highly unreadable fashion, but Riscy Pygness expresses the high-level definitions in straightforward Forth.

Gdb or Insight

On occasion it is convenient to be able to use the GNU debugger in a few cases in tracing out and correcting certain primitives.

To use the GNU debugger, you also need to install the gdb package for the ARM.

Insight is a GUI front end to gdb.

Tcl

The host-side of Riscy Pygness is written in Tcl. See riscy.tcl.

I suggest a recent version of Tclkit for your platform, stored in /usr/local/bin and symlinked (soft linked) to the name /usr/local/bin/tclkit. If you use a different path to Tcl, then edit the top line of riscy.tcl.

Flash Utilities (downloaders)

There are several ways to burn the Forth image into the target's flash.

The NXP LPC ARM (and other manufacturers') chips contain a serial bootloader.

A serial bootloader is a program residing in the target chip's flash that allows a flash utility on the host (the desktop PC) to burn a program into the target's flash memory via a serial line.

The host-side program is often called a flash utility or a downloader.

NXP has an official flash utility that runs only on Microsoft Windows. Fortunately, several people have written flash utilities that are more platform independent.

Another way of burning a program into flash is to use a JTAG cable in connection with a program on the host such as OpenOCD.

I have used the serial flash utilities by Martin Maurer and by Edwin Olson very happily, and sometimes OpenOCD with an Olimex JTAG cable.

running lpc21isp

I download code to be burned into the onboard Flash using the chip's built-in bootloader combined with Martin Maurer's lpc21isp flash utility program that runs under either Linux or Microsoft Windows.

Martin Maurer's lpc21isp is available from http://tech.groups.yahoo.com/group/lpc21isp (the Yahoo lpc21ispgroup) and a version is also included on the Riscy Pygness web site.

I run it to download the binary file kernel-lpc2106.bin, with the command

$ lpc21isp -verify -bin kernel-lpc2106.bin /dev/ttyS0 38400 14746

where /dev/ttyS0 is the serial port I am using and 38400 is the baud rate and 14746 is the speed of the crystal in KHz of the board I'm using (i.e., 14.746 MHz).

Compiling lpc21isp

Download the source, unzip it into /usr/local/src, then compile it, then soft-link it into the /usr/local/bin directory.

It is a very easy compile. Just unzip, cd to its directory, then type make. Then, make sure the resulting binary is in your path. (Of course, you have to have make and a C compiler installed.)

What I do is to unzip it (untar it, whatever) into /usr/local/src/ which creates a subdirectory named lpc21isp. Then I rename that subdirectory to include its version number, e.g.,

# mv lpc21isp lpc21isp_179

Then I cd to that directory and run make. This produces the binary /usr/local/src/lpc21isp_179/lpc21isp. Then I soft-link it to /usr/local/bin/lpc21isp so it will be in my path.

# ln -s /usr/local/src/lpc21isp_179/lpc21isp /usr/local/bin/lpc21isp

I am using version 1.79. Here is the source code:

http://pygmy.utoh.org/riscy/lpc21isp-1.79.tar.gz

You can look for newer source either in the Yahoo lpc21isp group or at http://sourceforge.net/projects/lpc21isp/.

Example of programming the LPC2106

First, orient your board so "Olimex" reads correctly from left to right, then the serial connector is at the top left and the power connector is at the top right and the blank wire-wrap area is at the bottom.

Here is how I have my board jumpered:

Then I open a terminal and get my lpc21isp command ready to go, but don't hit RET yet. Then, I short the BSL jumper. Then I press RET at about the same time I press and release the reset button on the board (RST is the small push button just to the right of the CPU chip).

After successfully downloading to the flash, the lpc21isp program may say it has jumped to the program, but of course it has not, and cannot, because BSL is still shorted. Once the flash has been programmed, I open the BSL jumper and press the reset button again. If all is well, the LED should blink 6 times (because riscy.asm contains the code to blink the LED 6 times).

Other bootloader flash utilities

- lpc2k_pgm
http://www.pjrc.com/arm/lpc2k_pgm
- lpc_prog
http://www.blisstonia.com/software/lpc_prog_20060709.tgz by Edwin Olson, license GPL, per his posting to the lpc2000@yahoogroups.com mailing list. (There may be newer versions by the time you read this). The '2006' is a typo in the file name and the referenced file is the version that was current as of July 9, 2007. (I have used the earlier version of 20070503 successfully with the lpc2378.)

Make

make is a program for executing commands based on declared dependencies. It allows you to say

$ make xxx.bin

and have the appropriate commands executed automatically to

However, make checks the timestamps on the files and only does the above steps that need doing.

See the file makefile in the distribution for the details. You will need to check the settings and adjust them for your environment, as described in the makefile variables section. For example, you will need to set the BIN variable to point to the correct path to your arm-elf-as file.

Terminal

You can run the smart terminal (i.e., riscy.tcl) directly from a terminal shell (a command line prompt) but it is often more convenient to run it from within Emacs. Emacs provides command history and command completion. Emacs even provides a text editor. See Emacs for some details about how to learn and use it.

Creating a new kernel image

Eventually, you may wish to modify the primitives (by editing riscy.asm and reassembling) or add additional high-level words to the kernel, then burn the new kernel into the ARM's flash.

The usual procedure is to edit riscy.asm (to change any primitives) and/or to edit kernel.fth (to change any high-level that will be part of the kernel), then to use the makefile to generate the new kernel image binary file (to be burned into flash) and the matching dictionary file.

Be sure to look through makefile and edit any settings that need to be changed for your environment (such as the serial port name or the CPU clock speed), then run

$ make

Which will do any pre-processing and assembly steps and then run riscy.tcl to create the new binary and dictionary files for all the supported boards.

Multitasking

Each task has a TCB (Task Control Block) in RAM. Each task also has its own data stack area and return stack area. The task's stacks would not necessarily need to be adjacent to the task's TCB, but that is where we place them. The address of the task is the start of its TCB. That is, the task is its TCB or the TCB is the task.

The UP register (we use R7 for this) holds the address of the current task (i.e., the address of the current task's TCB). UP stands for "User Pointer". It points to task-specific values — values local to the task.

The TCB has 3 named slots plus 5 unnamed slots. Each slot is 32-bits wide, thus the TCB takes 32 bytes. Traditionally, the TCB would have even more named slots. Feel free to add them if your application needs them, but these should do for most applications.

Traditionally, PAUSE would first store all the state onto the data stack and thus need just one slot to store the data stack pointer. However, to speed up PAUSE, we take advantage of the ARM's store multiple and load multiple instructions, at the cost of several extra slots in each TCB.

There is always at least one task. This is the "foreground" task. It and its TCB are configured automatically. The word PAUSE pauses the current task and turns control over to the next task. Each task, in its TCB, has a LINK slot that holds the address of the next task to run. When there is only one task, that task's LINK slot points to itself. Thus, when PAUSE executes, it saves the state of the foreground task, then immediately restores the state of the foreground task, and thus continues running the foreground task. That is, with only one task, PAUSE jumps back to the foreground task.

If you really don't plan to use more than a single task in your application, you could consider changing PAUSE to a no-op, as it doesn't really shine until more than one tasks are present.

Here is what the TCB looks like, with the offset relative to the start of the TCB (and thus relative to the value in UP):

   +    0   LINK   holds the address of the next task to execute
   +    4          holds saved TOS
   +    8          holds saved IP
   + 0x0C          holds saved DSTK
   + 0x10          holds saved RSTK
   + 0x14          holds saved RLOOP
   + 0x18   SP0    holds address of start of data stack
   + 0x1C   RP0    holds address of start of return stack
  often the data stack will start here (at offset 0x20)
  with the return stack starting just above the data stack
 

Creating a task

There is normally no need to create tasks, as three are created for you automatically.

See riscy.asm if you wish to create more or fewer.

Until/unless you initialize and wake TASK1 and TASK2, only the FOREGROUND task runs.

Suppose we want TASK1 to run a Forth word that increments a variable named TICKS every 1.5 seconds.

  VARIABLE TICKS
  : TICKER ( -)  0 TICKS !   BEGIN  1500 MS  1 TICKS +!  AGAIN  ;
  ' TICKER  TASK1  TASK!
  TASK1 WAKE
 

Let's take those lines one at a time:

- VARIABLE TICKS
this creates the variable (in RAM of course) that will be incremented
- : TICKER ...
this defines a Forth word named TICKER that first initializes the variable to zero. Then it runs a endless loop that, over and over, kills 1.5 seconds (1500 milliseconds) and increments the variable. Note that we did not put a PAUSE inside the loop. We could have, but it is not necessary in this case because the word MS includes a PAUSE. It is very important that each task PAUSE appropriately, otherwise, once that task gets control, it will never release control and no other task will be able to run. So, if you create an endless loop, if no word executed within your loop calls PAUSE, then you must put an explicit PAUSE in your loop.
- ' TICKER TASK1 TASK!
this tells the TASK1 what word it should execute. Note that we "tick" TICKER to get its address then store that word's address into TASK1.

Note also that we showed the initialization (' TICKER TASK1 TASK) as being done interactively. This is fine during testing. Normally, though, for an application, you will initialize the task within the definition of another word, probably your application's boot word, e.g.,

    : MY-APP ( -)
      ...
      ' TICKER  TASK1 TASK!
      ' SOMEOTHERWORD  TASK2  TASK!
      TASK1 WAKE  TASK2 WAKE
      ...
    ;
   

After initializing the task with TASK!, the task is still not running until it is awakened with WAKE. You can use the word AWAKE? to see whether a particular task is running. You can verify TICKER in TASK1 is running by checking the variable (TICKS ?) or by asking the task whether it is awake or not (TASK1 AWAKE? .).

- TASK1 WAKE
this starts the task running (by inserting it into the active task list).

Stopping and starting a task

The previous section showed how to create a task and how to start it. To stop a task by name, say TASK1 SLEEP then to wake it again, say TASK1 WAKE. Note that you cannot put the TERMINAL (i.e., the FOREGROUND) task to sleep.

A task can put itself to sleep with STOP.

Limitations and cautions

Switching back to interactive mode with ;;

When you are typing interactively or when you LOAD from a block, riscy.tcl needs to be able to tell whether to compile or interpret. It does this a string at a time. When typing, the string is the single line. When you LOAD a block, the string is the entire block.

The rule that riscy.tcl follows is that it starts each string in interpret mode and stays in interpret mode unless it sees a colon. Once it sees a colon, it switches to compiling mode and stays in compiling mode until it reaches the end of the string unless it sees a double semicolon (;;).

You rarely need to use ;;. Mostly, it would be used on a block that defined one or more words and then needed to switch back to interpreting. For most blocks, you would stay entirely in one mode or the other and would not need to use ;;.

Here is an example typed at the keyboard that would not work:

  : STARS  ( n -)  FOR  '* EMIT  NEXT  ;   3 STARS
 

Because it is all typed on a single line, it would switch to compiling mode when it saw the : and stay in compiling mode until the end of the string, thus it would not execute the 3 STARS part. You might be asking why the semicolon does not switch back to interpreting mode. The answer is that a single Forth word can have multiple exits.

The solution to the first example could be

  : STARS  ( n -)  FOR  '* EMIT  NEXT  ;   ;;  3 STARS
 

but that is awkward and unnecessary. Instead, just use two lines:

  : STARS  ( n -)  FOR  '* EMIT  NEXT  ;
  3 STARS
 

and no problem.

Interactive strings

If you type

  " THIS IS A STRING"
 

it will "interpret" the string, leaving the address of the string on the data stack. However, that won't be of much use, as the next thing you type will overlay the string. So, don't expect this to work

  " THIS IS A STRING"
  COUNT TYPE
 

The solution in this case is to do it all on a single line:

  " THIS IS A STRING"  COUNT TYPE
 

Of course, you could compile a string in a colon definition and the string would not get overwritten. You could do this:

  : MSG ( - a)  " THIS IS A STRING"  ;
  MSG COUNT TYPE
  MSG
  COUNT
  TYPE
 

C,

For now, at least, C, works only in non-interactive mode. So, it could still be useful in populating a table in flash, e.g.,

   TABLE XX
     $46 C,
     $47 C,
     $48 C,
     $49 C,
 

but cannot be used or tested when running interactively (an error message will suggest using , instead).

The work-around is to use , instead of C, and pad out the tail end of the table to a full-word boundary, e.g.,

   TABLE YY
     $49484746 ,
 

(Remember, the ARM in the LPC chips is little endian.)

Constants and LOAD

When generating a kernel image, LOAD requires an actual number, rather than a Forth constant. So,

3 LOAD

appearing on a LOAD block would cause trouble if 3 had been defined as a constant.

We are unlikely to need to load block -1 or block -2, so -1 and -2 are defined as constants.

We could define 0, 1, 2. 3 or whatever as constants if we changed the numbers passed to LOAD to actual numbers, e.g.,

03 LOAD

would work, providing there was no Forth word defined named 03.

Appendix

Reader prerequisites

To read this manual and to work with Riscy Pygness, you need to have a certain familiarity with computers, Linux, the command line, etc. You probably have that already if you are working with target ARM boards.

Nevertheless, here is a quick rundown on various topics with suggestions on where to get more information if you need it.

- terminal
This gives you a place to type commands. (Actually, the commands are typed into the shell program, typically bash, that is running in the terminal.) I usually run the program gnome-terminal for this, but any such terminal program will work.

In this manual, often the commands to be typed will be shown preceded by $ or #. Do not type this initial $ or #. It merely represents the terminal's (the shell's) prompt. # indicates the command is typed as the superuser. $ indicates the command is typed as an ordinary user. Note that a # appearing later in the command is completely different: it means the rest of the line is a comment (and you would not type the comment).

- command line
See terminal above
- command prompt
See terminal above.
- shell
See terminal above.
- becoming root
Some commands need to be typed as the superuser (also known as root).

Some Linux distributions use a root account with a password. In that case, type su then enter the password when prompted.

In other Linux distributions, you may need to type something like:

      $ sudo -i
    

or

      $ sudo su
    

Otherwise, look for how to do it in your distribution's documentation or on Google.

- unzipping
I use this as a general term for uncompressing a compressed file, regardless of whether it was zip'd, tar'd, gzip'd, bzip2'd, etc. The extension on the file generally indicates which program you should use. Here are some examples:
- xyz.zip
unzip xyz.zip
- xyz.tar
tar -xvf xyz.tar
- xyz.tar.gz
tar -xzvf xyz.tar.gz
- xyz.tar.bz2
tar -xjvf xyz.tar.bz2

If in doubt about the type of file, run the file command on it. E.g., file xyz.zip should indicate that it is a zip file.

- soft linking
This is sometimes referred to as a symlink or a symbolic link. It let's you store a file somewhere (perhaps /usr/local/src/lpc21isp_179/lpc21isp) yet refer to it via a directory that is in your path, such as /usr/local/bin/lpc21isp.

We set up the above link this way (as root):

    # ln -s /usr/local/src/lpc21isp_179/lpc21isp  /usr/local/bin/lpc21isp
   

If we later install a newer version, say as /usr/local/src/lpc21isp_180/lpc21isp, we can change the soft link with

    # ln -sf /usr/local/src/lpc21isp_180/lpc21isp  /usr/local/bin/lpc21isp
   

(Here we added the -f option to "force" the overwrite of the existing link.)

Another place we do this is with Tclkit.

- path
This refers to a file name, including the directory information needed to find it. The path can be absolute, such as /usr/local/bin/tclkit (note the leading /) or it can be relative to the current directory, such as tclkit (note the absence of a leading /) if the current directory were /usr/local/bin/ or bin/tclkit (note the absence of a leading /) if the current directory were /usr/local/.
- target
This is your ARM board
- host
This is your Linux desktop (or laptop or whatever) computer
- local documentation
The typical Linux computer is filled with documentation.
- external documentation
Google or some other search engine should take care of anything else. For example, in the Getting started section, there is a mention of shell aliases. If you do not know what that means, you could Google for "shell aliases".
- keystrokes
This manual uses the Emacs convention for representing keystrokes. For example, C-c means to press and release the "c" key while holding down the Control key. Below are some more examples, but for a full description, run the Emacs tutorial (from with Emacs).
- C-h
while holding down the Control key, press and release the "h" key.
- C-t
Control-t (within Emacs, this starts the tutorial)
- RET
press the Return (or Enter) key
- M-x
while holding down the "meta" key (i.e., the Alt key on most keyboards), press and release the "x" key.

Board notes

Olimex LPC-P2103 board

Riscy Pygness works well on this chip and board. The main Riscy Pygness download also includes led1-2103.asm as a small test program to flash the Olimex board's LED, as a way of verifying the board and your connections to it.

What about board jumper settings? This is what worked for lpc21isp:

- DBG_E
shorted or open
- JTAG cable
plugged in or not
- JRST
open
- BSL
shorted when running lpc21isp but open to then run the Forth

Note, however, that if OpenOCD had been running, you have to power off the ARM board after killing OpenOCD. It is not enough to merely press the reset button; the board must be power cycled.

For JTAG, something like

$ openocd -f interface/olimex-jtag-tiny.cfg -f target/lpc2103.cfg

might do it. Note, though, that the lpc2103.cfg file has this line

flash bank lpc2000 0x0 0x8000 0 0 0 lpc2000_v2 12000 calc_checksum

but my Olimex LPC-P2103 board has a 14.7456 crystal.

So, I created a custom lpc2103.cfg file in my working directory that changes the flash line to

flash bank lpc2000 0x0 0x8000 0 0 0 lpc2000_v2 14746 calc_checksum

and I also added this line

jtag_khz 50

to slow down the interface enough that it would work.

For this to work, I short the DBG_E jumper (and, of course, I plug in the JTAG cable).

Note, sometimes I need to start the openocd program more than once before it finally comes up without errors. I suppose I could make it more likely to work by reducing the jtag_khz 50 line even further.

The button on this board is normally open. The button signal is pulled up, so it is normally high. When the button is pressed, it shorts the button signal to ground. The button signal is connected to U1 (the LPC2103 chip) pin 45, P0.15/EINT2/RI1.

See custom-lpc2103.asm. That pin (P0.15), as with all the I/O pins, has been configured as an I/O pin and is an input pin, which is just what we want. We also use the fast I/O facility.

So, for example, to read the button we could use

   : BIT15 ( - mask)  $00008000  ;
   : BUT? ( - f) FIO0PIN @ BIT15 AND 0= ;  ( returns true if the button is pressed)
   

LPC2106

Here are some thumbnails (just click on them to see the full size images) of the Olimex LPC-P2106 board and my MMC/SD interface to it.

LPC2378 (LPC23XX)

The main Riscy Pygness download includes led1-2378.asm as a small test program to flash the Olimex board's LED.

Pin-out diagrams for the connectors on the Olimex LPC P2378 development board.

Tools summary

To generate Riscy Pygness from the bottom up, the tools needed are

- binutils for the ARM
Note this provides the cross-assembler (and the cross-linker, etc.) It runs on the host but handles assembling and linking for the target ARM CPU. This package provides the assembler, linker, object file manipulator, etc. It is very easy to compile the binutils package. See the GNU Toolchain section.
- make
One way of looking at the make program is that it remembers the complicated steps that need to be taken so that you don't have to remember them. The file makefile is where these details are stored.
- downloader
You need something to burn a binary image into the ARM's flash (OpenOCD with JTAG, lpc21isp, etc.). I usually use lpc21isp for this.
- Tcl
I use Tclkit, a single-file distribution of Tcl/Tk. (We don't need Tk.) There are versions of Tclkit for lots of different machines. I put the one for my machine in /usr/local/bin/ and symlink it to the name tclkit. (See the first line in the file riscy.tcl.)

FAQ

- Where is the Forth compiled?

The Forth code is compiled on the host (i.e., the desktop PC). The Tcl program riscy.tcl serves two purposes, controlled by options on the command line.

- Give me a quick overview of Riscy Pygness

The Riscy Pygness system involves software on the host and on the target:

- target
A Forth image that runs on a target board (with an ARM CPU).

The image can be thought of as having 3 pieces:

  • primitives written in ARM assembly language in flash,
  • high-level Forth in flash,
  • high-level Forth in RAM.
- host
An assembler and compiler and smart terminal that run on the host (i.e., your desktop PC).

The assembler and compiler let you generate new Forth images for the target's flash and the compiler lets you extend the target's Forth dictionary by defining new words interactively into RAM.

The smart terminal provides the communication link over a serial port through which you interact with the target, exercising words in the target's dictionary and/or defining new words.

- Do I need to install the entire GNU Tool Chain?

No, but you must install at least binutils for the assembler and related utility programs if you wish to make changes to the primitives (in riscy.asm).

You do not need to install GCC.

- Do I need to install Tcl on the host?

Yes. You need Tcl but not necessarily Tk. The easiest way is download a version of Tclkit and (as root) put it in the directory /usr/local/bin. Then soft link it to the name tclkit. The top line of riscy.tcl expects it in that location with that name. Otherwise, edit the top line of riscy.tcl accordingly.

- Are the new high-level Forth words stored in on-chip Flash?

As you work interactively, new words that you define are stored in RAM. If you reset your target board or power it off, the definitions vanish from the target's RAM. So, rather than typing lots of definitions interactively, you should type them into a file and then load them from the file. For example, riscy.tcl maps (allocates) the block range 2000 through 2999 to the file s2.fth. You could use this file for your application. Set up block 2001 as your load block. Put some code you wish to load into RAM into block 2008. Then, say 2008 LOAD to load block 2008. (Eventually, you would edit block 1 in kernel.fth so it would load your application when it creates a kernel image.)

As you finish testing parts of your application, from time to time you will want to generate a new Forth image for the flash memory that will contain your new/tested words.

- Once the compiler combines the primitives with the high-level Forth words and produces a kernel image that you burn into the Flash, can you extend the system with additional words interactively?

Yes! When you are in the interpreter and type a colon at the beginning of a line, the interpreter knows to compile that line (thus updating the target image on the host) and then to send that target image extension to the target for it to program into its RAM. Also, you can compile a region (some text that you have highlighted) in your editor on the host (if you are running Emacs) and have that region compiled and sent to the target with an Emacs keystroke (I suspect that this is a feature planned for the future).

- What files need to be modified to port Riscy to new hardware ... i.e., if I have an LPC2124 based system?

See makefile and the various *.asm files to see how customizations are done for different ARM variants.

First, study the manual for the LPC2124 to see how closely the configuration addresses (for timers, I/O ports, interrupts, etc.) match those of the LPC210x chips, etc. As long as the symbols do not conflict, just extend equates-lpc2xxx.asm to include any new symbols needed. Then create a new file named something like custom-lpc2124.asm and olimex-lpc2124-equates.asm with the CPU or board-specific information such as what pin is connected to the LED. Start by copying one of the other custom-*.asm files. The idea is to localize chip-specific items in these files that riscy.asm includes.

The main source code files are riscy.asm (for primitives) and kernel.fth (for essential high-level Forth code). CPU-specific items go in a CPU-specific assembly file (see custom-lpc2106.asm, custom-lpc23xx.asm, etc.). Here are the items that come to mind (but let's extend them as we run across others):

JTAG and OpenOCD

ARM chips typically have a JTAG interface. To use it, you need a JTAG cable. Olimex and SparkFun sell several inexpensive JTAG cables that work fine. I started with a parallel port cable but now I use the Olimex ARM-USB-TINY JTAG cable.

You need some software also, in the form of OpenOCD. You can use OpenOCD in two ways. In either case, you first start it running in a terminal.

Starting OpenOCD

Fortunately, OpenOCD comes with a good user manual. OpenOCD comes with some prewritten configuration files. If configuration files for your board and/or ARM variant are present (in /usr/share/openocd/scripts/ on an Ubuntu system), running it can be as simple as specifying one config file for your JTAG cable and another for your board, e.g., if you use the same JTAG cable I use and an Olimex LPH-H2148 board, you would start OpenOCD in a terminal with this command:

 $ openocd -s /usr/share/openocd/scripts -f interface/olimex-jtag-tiny.cfg -f board/olimex_lpc_h2148.cfg
 

OpenOCD version 0.2.0-in-development (2009-06-30-01:11) does not come with configuration files for the Olimex LPC-P2106 board or chip, so I created a target config file named lpc2106.cfg based on a similar target config file. I hope to post it to the OpenOCD site, so perhaps it will be included in a future OpenOCD version. Meanwhile, you can use it directly from within your working directory. The idea is that its path would eventually be /usr/share/openocd/scripts/target/lpc2106.cfg.

Rather than typing a long command line such as

 $ openocd -f /usr/share/openocd/scripts/interface/olimex-jtag-tiny.cfg -f lpc2106.cfg
 

I created a config file named openocd2106.cfg that goes in my working directory. The file, at a minimum, has these contents:

# This file is for use with the Olimex LPC2106 board.
# It is named openocd2106.cfg.  Run it this way:
#    $ openocd -f openocd2106.cfg

# This is the JTAG connector I use
source [find interface/olimex-jtag-tiny.cfg]

#  following file, for now, is in my current directory
source [find lpc2106.cfg]
 

Once you run openocd -f openocd2106.cfg in a terminal, OpenOCD is running as a daemon. To kill it, type C-c in the terminal.

Sometimes I need to start it several times before it comes up without any error messages. (Start it, see errors, kill it. Start it, see errors, kill it. Start it, no errors, good, leave it running.) You need to have the JTAG cable connected, and the DEBUG jumper (labeled "DEBUG" on the LPC-P2106 board) shorted.

Running OpenOCD

After starting OpenOCD running as a daemon as described in the previous section, you can open a new terminal and connect via telnet;

   telnet localhost 4444
 

See the OpenOCD manual for the commands you can use. Following are some examples.

Miscellaneous
  > poll
  > reg
  > reg r0
  > reg pc
  > reg r0 0x12345678
  > reg r0
  > halt
  > resume
  > step [address]
  > step 0
  > reset
  > reset run
  > reset halt
  > (for memory display of word, half-word, or byte, use 'mdw addr [count]' etc.)
  > mdw 0 8
  > mdb 0 32
  > (for writing, use 'mww addr value', 'mwh addr value', 'mwb addr value')
  > mww 0x40000000 0x12345678
  > mww 0x40000004 0x55553333
  > mdw 0x40000000 4
  > armv4_5 disassemble 0 20
  > arm7_9 fast_memory_access enable
  > flash banks
  > flash info

 
Dump the flash contents

Here is an example to verify the contents of the flash. I (thought I) had programmed the flash with the file kernel-lpc2106.bin, a 3584-byte file at the time. So, I wanted to dump the first 3584 bytes of the flash to another file so I could compare the two files.

I dumped the flash to a file named dump1.bin, via OpenOCD with

  $ dump_image dump1.bin 0 3584
 

Then I compared the original file kernel-lpc2106.bin with dump1.bin with the hexdiff program, i.e.,

  $ hexdiff kernel-lpc2106.bin dump1.bin
 

Note, if the only difference is the 4 bytes at address 0x00000018, that is not necessarily a problem. Those 4 bytes hold a checksum (the 32-bit two's complement of the sum of the other 7 vectors) used by the LPC2106 bootloader. So, if you add all 8 vectors, you should get a number whose rightmost 32 bits are zeroes. riscy.tcl calculates this checksum automatically when it creates a kernel image.

Erase flash sector 0
  flash erase_check 0
  flash protect_check 0
  flash info 0
  flash protect 0 0 0 off    # if sector 0 had been protected
  flash protect_check 0
  flash info 0
  flash erase_sector 0 0 0
 

The first command lists the sectors of bank 0 to see which are already erased. The second erases, in bank zero, all the sectors from number zero through number zero. I.e., it erases sector 0.

Note, on the LPC-P2103 (and probably on the LPC-P2106) it never shows the protection turned off, yet that did not prevent me erasing or reprogramming sectors.

Reprogram the flash
  flash write_bank 0 kernel-lpc2106.bin 0
 

This reflashes the chip with the Riscy Pygness kernel into bank 0 starting at offset 0.

GDB

For tracing through tricky assembly language routines, you might find GDB (the GNU Debugger) useful. It can be run various ways. I often use it from within Emacs.

To use GDB with a JTAG connector, first start OpenOCD as described in the JTAG and OpenOCD section. Then instead of connecting to OpenOCD via telnet, connect to it via GDB.

Before running GDB for the first time, take a look at the included .gdbinit file. You may need to edit it for your system. For example, it contains a line to change to my working directory and to open a file such as riscy-lpc2103.elf. Be sure to change those lines to your working directory and to the *.elf file that you will be debugging.

Then, in Emacs, start GDB with M-x gdb. It will prompt you for the GDB command to use. I use

arm-elf-gdb --annotate=3

A common error is to run gdb (which runs the GDB for your desktop CPU) rather than running arm-elf-gdb (which runs the cross-target GDB for your ARM CPU).

In the Emacs *gud* buffer, you can issue GDB commands directly or you can issue OpenOCD commands by preceding the OpenOCD command with the GDB command mon. E.e., to run the OpenOCD poll command from within the *gdb* buffer, type mon poll.

OpenOCD (and thus GDB) has a limit of two hardware breakpoints. So, when debugging in flash, you need to be careful not to set more than 2 breakpoints at a time.

Example

Here's an example of using GDB to step through the Forth word C@ on the Olimex LPC-P2103 board, which uses the NXP LPC2103 ARM chip.

First start OpenOCD as described earlier. I open a terminal, cd to my working directory, and start OpenOCD with

$ openocd -f openocd2103.cfg

If it reports errors, kill it (with C-c) then try again, until it comes up with no errors. I have sometimes needed to alter the jtag_khz xx setting to slow down the clock, e.g., jtag_khz 32. Once it starts sucessfully, you can ignore this terminal.

Then start up Emacs in another terminal (or wherever), if not already running. Edit the .gdbinit file in the working directory to be sure GDB will cd to your correct working directory and to open the correct *.elf file — look for a line like

file ~/riscy/lpc2103/riscy-lpc2103.elf

Then, start GDB with M-x gdb and specify

arm-elf-gdb --annotate=3

as the actual GDB command to run.

At this point, we have one terminal running OpenOCD as a daemon in the background. This terminal does not need any further attention. Then, we have Emacs running (in a terminal or in its own window) with GDB running in the Emacs *gud* buffer, where we will interact directly with GDB.

We then start another terminal in which to run Riscy Pygness. This terminal may actually be a shell running inside another instance of Emacs (to give us convenient command recall and command completion) or it could be your usual terminal program (such as gnome-terminal). At some point, we start Riscy Pygness in this terminal, with

$ ./riscy.tcl -image kernel-lpc2103 -port /dev/ttyS0

Since we wish to step through the code for C@, we need to find its address and tell GDB to set a breakpoint there.

There are two ways to find this address. One is from within Forth by running ' C@ .H (which prints the address in hexadecimal). The other is by consulting the linker listing from when the Forth kernel was generated. In this example, that file is named riscy-lpc2103.lnkt. We won't find C@ listed under "C@" here (although we would find DUP listed under "DUP"). The reason is that "C@" is not a valid label name to the assembler. If we search for "C@" in riscy.asm, we will discover the label name used for the Forth word C@ is "Cfetch". (Thus, we would search for "Cfetch" when looking in the linker file.) In this example, its address is 0x0374. Since our .gdbinit file sets the radix to hexadecimal, within the *gud* buffer, we can specify that address as 374.

Note, since we told Emacs to open the file riscy-lpc2103.elf it knows to find the source code in the file named riscy-lpc2103.s (which was derived by preprocessing the file riscy-lpc2103.asm which was derived from riscy.asm by including the proper include files for the LPC2103 chip). As you jump and step within the *gud* buffer, a marker should move around accordingly in the riscy-lpc2103.s buffer.

In the Emacs *gud* buffer, set the breakpoint:

  mon poll        # make sure the ARM is halted
  mon reset halt  # in case above showed it was not halted
  i b             # show current breakpoints
  del 2           # delete any other than the one at _start (at 0x20)
  i b             # confirm only the one at 0x20 is set
  b Cfetch        # set our second breakpoint at the start of the C@ primitive
  j _start        # jump to the start of the program
  c               # "continue" to start the program running so our Forth buffer can run
 

Then, in the Forth buffer, type some command involving C@ to make it hit the breakpoint, if it has not already stopped at that breakpoint, such as $4444 C@.

Then, back in the Emacs *gud* buffer, examine registers, single step, etc. See the GDB users manual for details.

  p $r0           # display register zero (which holds the top-of-stack)
  s               # single step
  ...
  i reg           # show values (and names) of the CPU registers
  p $cpsr         # display the condition code register
                  #   the left 4 bits (NZCV) are often the most interesting
                  #     bit 31  N
                  #     bit 30  Z
                  #     bit 29  C
                  #     bit 28  V
                  # e.g., 0x200000d3 means the carry bit is set
  display $r1
  display $r2
  display $cpsr
  display $r0     # set up the 4 values we wish to see after every step command

 

Emacs

Learning Emacs

Emacs has extensive built-in help. If you are not yet familiar with Emacs, start it up and work through its built-in tutorial. Start the tutorial by pressing C-h t (i.e., press control h then press t) or by clicking on the Help menu and choosing Emacs Tutorial.

Emacs mode for Riscy Pygness

The file forthblocks.el (in the distribution) is an Emacs mode for editing Forth. Put that file into your home directory.

Put the following into your ~/.emacs file so the forthblocks mode will be run automatically when you open a file whose name ends in .fth.

(setq auto-mode-alist
      (cons '("\\.fth" . forthblocks-mode) auto-mode-alist))
(autoload 'forthblocks-mode "~/forthblocks.el" "Forth blocks editing mode." t)

Read the file forthblocks.el for details. The quick summary is that it works with plain text files (not traditional Forth 1024-byte blocks) but simulates blocks by recognizing comments such as

( block 1   ------------------  load block)
( shadow 1 )
( block 2  miscellaneous)
( shadow 2 miscellaneous )

as block markers. This gives you "logical" blocks which should be kept short, but are variable length. For me, this gives a pretty good compromise for source code: modular Forth blocks and the ability to use my favorite text editor.

See forthblocks.el for the keystrokes that move from block to block, that toggle between a block and its associated shadow block, that renumber the blocks, etc.

If you really don't like even this form of blocks, then just put a single block comment at the top of the file, such as

( block 2000   everything in this file is in block number 2000)

then everything in the file will be in a single (gigantic) block. To load the entire file, say 2000 LOAD.

See also riscy.tcl for how to map block ranges to file names.

Antecedents

The current version of Riscy Pygness was derived from various previous versions of Riscy Pygness and from Pygmy Forth for the PC (a 16-bit DOS Forth), which itself was derived from Charles Moore's cmFORTH.

However, Riscy Pygness is quite different from Pygmy Forth for the PC, not just because it is a 32-bit Forth for an entirely different processor, but also because it borrows a few ideas from Charles Moore's colorForth.

colorForth

Chuck Moore's colorForth is a very interesting system. It has inspired some of the features of Riscy Pygness.

- Use of color to "tag" words in the source

Rather than tagging words in the source with color, Riscy Pygness tags words with tags, so to speak.

Specifically,

- Just two tasks

colorForth appears to have exactly two tasks, a foreground task and a background task.

Riscy Pygness also has a fixed number number of tasks (three in the current version, but this is adjustable).

- Some unusual simplifications

In colorForth, OR means exclusive OR and there is no inclusive OR

Riscy Pygness retains both the usual OR and XOR.

- Multiple entry points and exit points and optimized tail recursion

Yes, Riscy Pygness does this also.

GNU Toolchain

Installing the GNU Toolchain

In order to create a new kernel, you need to have at least parts of the GNU Toolchain for the ARM installed. In particular, you need the assembler, linker, and some object file manipulating utilities — all of which are bundled together in the binutils package.

Your two main choices are to install a pre-compiled GNU tool chain or to compile the binutils software yourself.

I tend to recommend compiling binutils yourself rather than installing a precompiled version.

Binutils is very easy to compile. That is the only part of the tool chain you really need in order to work with Riscy Pygness. You might also wish to compile the GNU debugger. You do not need the C compiler for the ARM.

Compiling the Tool Chain

If compiling from source, you might find the Lewin Edwards book Embedded System Design on a Shoestring helpful, although it isn't needed if you are compiling just binutils.

Here is an example of how to compile binutils.

First, download the source from http://ftp.gnu.org/gnu/binutils/, choosing one of the more recent versions, such as http://ftp.gnu.org/gnu/binutils/binutils-2.18.tar.bz2.

In general, for each package, I download the *.tar.gz or *.tgz source code package and uncompress it in /tmp, e.g.,

     # cd /tmp
     # tar -xzvf binutils-2.15.90.0.1.1.tgz
   

then create a build directory at the same level, e.g.,

     # cd /tmp
     # mkdir binutils-make
   

then configure, compile, and install the package, with something like the following

     # cd binutils-make
     # ../binutils-2.15.90.0.1.1/configure --target=arm-elf --prefix=/usr/local/arm
     # make
     # make install
   

Of course, adjust the file names to suit the actual version you are compiling and installing.

Note, you must be root, at least for the install step. See the section on becoming root.

Then, if you want to compile gdb or insight (insight contains gdb, so no need to compile both of them), do it generally as above but with a configure command such as

   # ../insight-6.1/configure --target=arm-elf --prefix=/usr/local/arm/
   

Makefile automatic variables

This is a quick reference to the meaning of some automatic variables used in a makefile.

Given a rule such as %.s: %.asm which says how to make an assembly file suitable for passing to the GNU assembler (e.g., led1.s) from a preprocessable assembly source file (e.g., led1.asm),

- $*
the stem (e.g., led1)
- $@
the target file (e.g., led1.o)
- $<
the source file (e.g., led1.s), i.e., the first prerequisite
- $^
the list of all the prerequisites

Troubleshooting

Bad flash

One symptom might be that things suddenly make no sense.

You've been working away with Riscy Pygness and your target ARM board, then you make a change, reflash the target, and nothing seems to work quite right. What could it be?

I'm not even going to mention the possibility that you forgot to turn on the power or perhaps left the boot jumper shorted after flashing.

One possibility is that the target's flash did not actually get programmed correctly. I believe I had this happen when flashing with version 1.48 of the lpc21isp, even though I used the -verify option. lpc21isp should have complained, but, no, it just silently pretended the flashing had succeeded.

To verify the flash was not programmed correctly, I used the JTAG interface and OpenOCD. First, I was single stepping through the code and the wrong value for the flash token table appeared to be loaded. That was my first clue.

Then, I used OpenOCD to dump the actual contents of the flash to a file and then compared that with the original kernel-lpc2106.bin, using a program named hexdiff. The two files should have been identical (other than perhaps the checksum vector at address 0x00000018), but they were not.

To fix it, I then used OpenOCD to erase the flash and then used OpenOCD to reprogram the flash. Then things worked properly once more.

See the JTAG section for details on how to dump, erase, and reprogram the flash.

I upgraded to version 1.79 of lpc21isp and tried again with the same board. This version, fortunately, actually verifies the flash. It warned me that it couldn't program a sector, rather than failing silently.

Blinking LEDs

See the label blink: in riscy.asm. This routine blinks an LED on the target board (if the board and the custom-*.asm file support this). Near the start of the routine the count is set. You can change how many times the LED blinks. If, when you reset the board, you see the LED blink the appropriate number of times, then you know the program has progressed to that point.

Serial port problems

As distributed, Riscy Pygness is set to use a serial port speed of 38,400 bps. This is a conservative value and you probably could increase it to 115,200 bps. If you change it, be sure to change it everywhere: makefile, riscy.tcl, and custom-*.asm.

See the label greet: in riscy.asm. Prior to blinking the LEDs, the target sends a greeting out the serial port. The greeting consists of a CRLF (carriage/linefeed), followed by "hi", followed by CRLF.

In normal use, i.e., starting the program with

./riscy.tcl -image kernel-lpc2103 ...,

you will not see this greeting because riscy.tcl silently eats the greeting (since it is waiting for a properly formatted message from the target and the greeting is deliberately not so formatted).

However, if you are having trouble with the serial communication, you can start a serial terminal program such as minicom instead of riscy.tcl. Then, when you boot or reset the target, you should see the greeting in the serial terminal. If you do not see the greeting, check your cables and check your settings in minicom. Are you using the correct serial port name? Have you set the baud rate correctly (this should be 38,400 bps unless you have changed it everywhere)? 8 data bits, no parity, 1 stop bit? Have you turned off both hardware and software flow control? Until you see the greeting, there is no point in wondering about why the Forth itself doesn't work.

I have run into the problem after rebooting the host where the host programs (either riscy.tcl or lpc21isp) were unable to set the baud rate. With riscy.tcl, the symptom is that, although a message such as

   Welcome to Riscy Pygness
   Loading the dictionary from kernel-lpc2103.dictionary
 

appears as usual, the expected prompt never appears. That is, this what should appear

   Welcome to Riscy Pygness
   Loading the dictionary from kernel-lpc2103.dictionary
   >
 

The cure for this is to bring up minicom and set the baud rate to 38400 then exit minicom. I imagine this will last until the host is rebooted. Thus, you probably only need to do this once each time you reboot the host.

Contact Information

Email: frank@pygmy.utoh.org

Home page: http://pygmy.utoh.org

Main Riscy Pygness page: http://pygmy.utoh.org/riscy