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.
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.
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
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).
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.tclscript 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 ofriscy.tclto 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
rwhich 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
~/.bashrcfile. Then, you can start Riscy Pygness by typing
$ r
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.
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).
$ cd # change to your home directory
$ mkdir riscy # create a subdirectory for Riscy Pygness
~/riscy$ cd ~/riscy # move to that subdirectory
$ unzip riscy-2010mmdd-hhmm.zip # substitute actual zip file name
Install Tcl and make the first line of riscy.tcl point to Tcl. See
the Tcl and path to Tcl sections for details.
If you don't have the make program installed, install it. If you
are on a Debian or Ubuntu system, you can use Synaptic or, as root
at a command line, type
# apt-get install make
You need some method to burn binary images into the flash memory on your ARM CPU. If you already have a method you are happy with, you can keep using it. Otherwise, the see lpc21isp and flash utilities sections and/or search this manual for "lpc21isp".
makefile
See makefile variables. The only ones you normally need to
verify/edit are CCLK, PORT, and BIN. (Forget BIN if have not
installed ARM binutils yet, see the next subsection.)
Once you have the variables in makefile set correctly, find the
correct kernel binary for your ARM board:
$ dir kernel-*.bin
For example, if you have the Olimex LPC-P2106 board, you would
choose the file kernel-lpc2106.bin. Then, burn it into the ARM
flash with your preferred method, or with lpc21isp with
$ make kernel-lpc2106.dl
Feel free to read makefile to see how the above command, which ends
in "dl" (which stands for "download"), causes lpc21isp to download
the file named kernel-lpc2106.bin.
See the Flash utilities section for more details.
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:
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.
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.
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.
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.
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
$ man make$ info makeor by googling (of course).
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
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.
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.
CCLKlpc_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/dev/ttyS0
or COM1: for the first serial port.BINarm-elf-as) and other binutils utilities such as
arm-elf-objdump.DLBAUDPREASM*.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).LNKFLAGSASMFLAGSLNKFLAGSZIPFILESmake zip to
create a local backup. You would not ordinarily need to do this.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.
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:
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
riscy.tcl program serves two purposes:
$ ./riscy.tcl -flash 1
but you would not run this manually. It would be done automatically by the makefile.
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.
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::debugDEBUG.::serialPort1/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.::baudratefilenameFromBlockNumberkernel.fth.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.
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.
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.
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).
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.
This is a batch mode. It runs entirely on the host and does not need a connection to the target. It combines the primitives with high-level Forth and saves the result into two files:
*.binkernel-lpc2294.bin, to be
burned into the target's flash.*.dictionarykernel-lpc2294.dictionary, for use when running in interactive
mode, to map Forth word names to target addresses.This is the interactive mode. This does require a connection to
the target. It is started by passing the name of an image, e.g.,
kernel-lpc2294, which causes it to load a dictionary file, e.g.,
kernel-lpc2294.dictionary, that matches the binary image running
on the target.
./riscy.tcl -image kernel-lpc2294 -port /dev/ttyS0
(Well, really, you would set up a shell script or shell alias to type the above line for you. See elsewhere in this manual.)
In this mode, the user's keyboard input is executed by the target. New words can be defined. Block files can be loaded. All new words defined reside in RAM.
The main development mode this is: Define and exercise new words. From time to time, when you are satisfied with the new words, generate a new flash image that includes the new words.
When running interactively, all numbers go to the target's data stack (when the host needs a number, it asks the target to send it back).
Suppose you would like to refer to the address of one the many ARM CPU configuration registers, such as PINMODE3. That configuration register address is 0xE002C04C. In Forth, you could say
$E002C04C
but your code would be more readable if you referred to it by name as in
PINMODE3
Yet, there are so many of these configuration registers (see the
file equates-lpc2xxx.asm), that you would hate to use dictionary
space on the target to define them all.
Fortunately, you do not need to define them. You can just use
them. riscy.tcl knows how to look them up automatically.
As riscy.tcl collects each word as it is compiling a string it
first checks to see if it an immediate word. If so, it runs just
on the host. Otherwise, it checks to see if it is a word that has
been defined for the target. If so, it compiles the word to be
sent to the target (either to be compiled or interpreted on the
target). Otherwise, it checks the list of know constants (where,
for example, it would find PINMODE3). If so, it compiles the
literal number. Otherwise, tries to view the word as a number of
some sort (for example, $03F8, 'e, or 79). If so, it compiles the
literal number. Otherwise, it reports an error.
EXIT when possible by
changing the previous call to a jump).FOR ... NEXT
: COUNT-DOWN ( u -) ?DUP IF DUP . 1- COUNT-DOWN ; THEN ;
: COUNT-DOWN ( u -) BEGIN ?DUP WHILE DUP . 1- REPEAT ;
Tokens use 13 bits (the most significant 13 bits of the token) to hold a token number, plus 3 bits for flags. The 13-bit token number is used to index into a table of addresses to look up a full 32-bit address (in either the flash token table or in the RAM token table).
Heads are stored only on the host, thus taking no space on the target chip.
Interactive poking at your hardware and testing snippets from the keyboard are perhaps the strongest advantages of Forth for developing embedded systems. These definitions go into RAM. We work awhile and then, from time to time, the results are incorporated into the main source code and a new kernel (flash image) is generated.
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.
For the assembler and gdb, see Installing the GNU Toolchain.
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.
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.
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.
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.
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).
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:
You can look for newer source either in the Yahoo lpc21isp group or at http://sourceforge.net/projects/lpc21isp/.
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:
Debug (upper right corner) is open.J1 (middle of top edge) is open.LED_J (just to the right of the red LED) is shorted.BSL (top left) is closed when programming the flash and open when
not programming the flashThen 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).
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
xxx.asm to produce xxx.sxxx.s to produce xxx.oxxx.o to produce xxx.elf (and produce a list of symbols)xxx.elf to produce xxx.binHowever, 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.
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.
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.
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.
LINKSP0SP! to reinitialize the task's data stack
pointer.RP0RP! to reinitialize the task's return
stack pointer.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
There is normally no need to create tasks, as three are created for you automatically.
FOREGROUND (also known as TERMINAL)TASK1TASK2See 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: TICKER ...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!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 withWAKE. You can use the wordAWAKE?to see whether a particular task is running. You can verifyTICKERinTASK1is running by checking the variable (TICKS ?) or by asking the task whether it is awake or not (TASK1 AWAKE? .).
TASK1 WAKEThe 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.
;;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.
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
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.)
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.
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.
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).
terminal aboveterminal above.terminal above.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.
unzip xyz.ziptar -xvf xyz.tartar -xzvf xyz.tar.gztar -xjvf xyz.tar.bz2If in doubt about the type of file, run the
filecommand on it. E.g.,file xyz.zipshould indicate that it is a zip file.
/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
-foption to "force" the overwrite of the existing link.)
Another place we do this is with Tclkit.
/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/.doc directory, e.g., /usr/share/doc/ and then for a
subdirectory named after the program you want documentation for,
e.g., /usr/share/doc/openocd/.man command (short for "manual"), e.g., man openocd or man
bash or man lsinfo command, e.g., info emacsapropos command, e.g., apropos terminal, to give you clues as
to which programs on your system my be appropriate.synaptic in a Debian or Ubuntu system). This can give you ideas
as to what programs (already installed or not) might be useful.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-hC-tRETM-xRiscy 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_EJTAG cableJRSTBSLlpc21isp but open to then run the ForthNote, 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)
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.
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.
To generate Riscy Pygness from the bottom up, the tools needed are
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.lpc21isp, etc.). I usually use
lpc21isp for this./usr/local/bin/ and
symlink it to the name tclkit. (See the first line in the file
riscy.tcl.)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.
It creates two files, e.g., kernel-lpc2106.bin (to be burned into
the flash) and kernel-lpc2106.dictionary (to be used by riscy.tcl
when running interactively).
This can be thought of as a batch mode. It combines the primitives with some essential high-level Forth code (plus any additonal high-level Forth code you wish to include).
The primitives are created by assembling the file riscy.asm
(actually the makefile takes care of this by assembling an
automatically-generated board-specific variant of riscy.asm such
as riscy-lpc2106.asm).
The high-level Forth code includes some essentials required to make a kernel that can extend itself. It can also include any additional high-level Forth code, perhaps your entire application, or perhaps just the well tested part of your application.
For example, the file kernel.fth contains the essential code
needed for the kernel. Its block 1 is its load block. If your
application is in the same file and you have edited block 1 to
also load your application, you could generate a kernel image
with
./riscy.tcl -flash 1
but you would ordinarily just make and let make generate the
kernel image automatically. See makefile, riscy.tcl, and
riscy.fth for more details.
This provides the smart terminal to let you interact with your
ARM board. When you start it, you specify the image on the
command line. For example, if you created a kernel image named
kernel-lpc2106 (consisting of the two files kernel-lpc2106.bin
and kernel-lpc2106.dictionary), and you are using the first
serial port, you could start it with
./riscy.tcl -image kernel-lpc2106 -port /dev/ttyS0
The Riscy Pygness system involves software on the host and on the target:
The image can be thought of as having 3 pieces:
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.
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.
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.
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.
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).
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):
custom-lpc23xx.asm for an
example.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.
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.
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.
> 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
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.
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.
flash write_bank 0 kernel-lpc2106.bin 0
This reflashes the chip with the Riscy Pygness kernel into bank 0 starting at offset 0.
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.
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 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.
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.
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.
Chuck Moore's colorForth is a very interesting system. It has inspired some of the features of Riscy Pygness.
Rather than tagging words in the source with color, Riscy Pygness tags words with tags, so to speak.
Specifically,
$03F8),'A is the number 65),So, other than the verbosity of using symbols rather than color for tags, this is similar to both colorForth and classic Forth.
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).
In colorForth, OR means exclusive OR and there is no inclusive OR
Riscy Pygness retains both the usual OR and XOR.
Yes, Riscy Pygness does this also.
; to represent the
exit).EXIT. In the source code of the compiler, you will see
the definition of cut (which I believe was written as // in
colorForth and/or some of Chuck's other Forths), which is used to
mark special cases where the use of a semicolon must not cause
the preceding subword to be changed to a jump (i.e., where an
actual EXIT must be compiled), such as after a THEN.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.
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/
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),
$*led1)$@led1.o)$<led1.s), i.e., the first prerequisite$^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.
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.
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.
Email: frank@pygmy.utoh.org
Home page: http://pygmy.utoh.org
Main Riscy Pygness page: http://pygmy.utoh.org/riscy