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 NXP (formerly Philips) chips
| variant | flash | RAM |
|---|---|---|
| LPC2101 | 8 KB | 2 KB |
| LPC2102 | 16 KB | 4 KB |
| LPC2103 | 32 KB | 8 KB |
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.
The ideal way to run it is to use Linux or Unix. This can be your main computer or a spare computer or even (via a live CD or a USB stick) a temporary computer.
There is no particular reason you couldn't use a Microsoft OS, but I think you will find it easier to use Linux, at least to start with. If you don't already have a Linux computer, you can boot your computer temporarily to Linux using one of the Live CDs (either from an actual CD or from a USB stick), without disturbing your main computer or its hard drive. Another alternative is to dedicate an old computer to this purpose. *Choosing a computer for the host describes several approaches.
If you are new to computers, Linux, and the command line, it might make reading this manual easier to take a quick look at the Linux and the command line section of the Appendix.
If you already have an Intel (or AMD) i386 machine (i.e., an ordinary "PC") running a 32-bit version of Linux, you are all set.
If not, and if you would rather not replace the OS on your main machine with a 32-bit Linux, then there are two main choices to consider:
There are several ways to do this, such as
You might tire of installing the bundle of tools everytime you boot, so I suggest putting the live CD onto a USB stick. See Putting Ubuntu on a USB stick. That way your the changes you make will be persistent.
Install Linux on some other computer than your main workstation. Then
ssh
program (the modern equivalent of
telnet
) or with VNC.
Once Linux was installed, you wouldn't particularly need to leave a monitor or keyboard attached to the spare computer.
This spare computer can be almost any old junk computer you have lying around. In a pinch, you could get by with 128M of RAM and a small hard drive and possibly run Linux without a GUI (without the X Window system). It needs a serial port, but you could use a USB-to-serial cable.
To run Riscy Pygness, the computer needs to have certain tools installed, such as Tcl, an ARM cross assembler and linker, make, and a downloader (to program a binary image into the ARM CPU's flash memory).
This step typically needs to be done just once.
The easy way is to download http://pygmy.utoh.org/riscy/arm-toolchain.tar.bz2 then uncompress it.
$ wget http://pygmy.utoh.org/riscy/arm-toolchain.tar.bz2 $ sudo tar -xjvf arm-toolchain.tar.bz2 --absolute-names --keep-old-files
The first line above downloads a bundle of tools and the second line uncompresses the bundle and places the tools where they should go.
It installs the following
Note, this bundle of tools is compiled for a 32-bit version of Linux running on Intel (AMD, etc.) hardware.
The hard way would be to install and/or compile and install those tools yourself, one by one. In which case, you still might wish to download the bundle of tools and then run the following command to see which files and needed and where they go:
$ tar -tf arm-toolchain.tar.bz2
You also need to have an editor (I recommend Emacs) and the Make program. Maybe your version of Linux already has one or both of these. You can find out if they are installed by running
$ which emacs $ which make
If not installed, install them with the package manager that your version of Linux uses. For example, if you are running Debian or Ubuntu, you could install them with
$ sudo apt-get update $ sudo apt-get install emacs $ sudo apt-get install make
Download the current version named something like
riscypygness-20101113.tar.bz2
from http://pygmy.utoh.org/riscy/ then
uncompress it. Here is an example (but change the file name to use
the current version shown on the web site):
$ cd # move to your home directory $ wget http://pygmy.utoh.org/riscy/riscypygness-20101113.tar.bz2 $ mkdir riscy # create a directory for your Riscy Pygness work $ cd riscy # change to that directory $ tar -xjvf ../riscypygness-20101113.tar.bz2 # uncompress the files
You will need to customize a few variables in makefile
to tell it
which ARM CPU variant you are using and which serial port is connected
to the ARM board. See *makefile variables.
One of the files is named .emacs-example
. If you do not already
have a .emacs
file in your home directory, you could use it to get
you started:
$ cp ~/riscy/.emacs-example ~/.emacs
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 program
lpc21isp
. 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
.
This 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
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 Riscy Pygness). 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.
Even if you are a vi
enthusiast, you might like to use Emacs to edit
Forth source code, perhaps by installing the Emacs viper package so
you can use your familiar vi
keystrokes.
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 if you wish 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 riscy.tcl
has
already read it into memory (on the host), otherwise it becomes the
current file and is read into memory. Then, 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 the possibility of using certain block ranges for text-file-based blocks for source code storage while 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. Then
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.
Why would you wish to create a new kernel? There are serveral reasons:
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.
LINK
SP0
SP!
to reinitialize the task's data stack
pointer.
RP0
RP!
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
)
TASK1
TASK2
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 TICKSthis 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 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 WAKEthis starts the task running (by inserting it into the active task list).
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
.
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:
*.bin
kernel-lpc2294.bin
, to be
burned into the target's flash.
*.dictionary
kernel-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 it the name of a kernel
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 Forth words can be defined. Block files can be loaded. All new words defined reside in RAM on the target.
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 kernel (to be burned into flash) 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, 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 known 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. If so, it compiles the literal number. Otherwise, it
reports an error.
Here are some examples of literal numbers:
$03F8 't 79
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 matching dictionary file, such as
kernel-lpc2103.dictionary
that the host uses to map Forth word names
to their addresses on the target.
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.
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 make
or by googling.
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 to the LPC-L2294 ARM board, 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.
To assemble riscy.asm
to produce the riscy.bin
file of primitives
that will go into the Forth kernel, just type
$ make
Make will assemble riscy.asm
and combine it with some high-level
Forth to produce a kernel consisting of a *.bin
file and a matching
*.dictionary
file.
The process is slightly more complex in that make
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 type
$ 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
and PORT
.
CCLK
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
/dev/ttyS0
or COM1:
for the first serial port.
BIN
arm-elf-as
) and other binutils utilities such as
arm-elf-objdump
.
DLBAUD
PREASM
*.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
ASMFLAGS
LNKFLAGS
ZIPFILES
make bzip
. 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, i.e., the smart terminal.
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 could 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
::debug
DEBUG
.
::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.
::baudrate
filenameFromBlockNumber
kernel.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, it 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" so the
line becomes
.equ lpc2106, 1
in a new file named riscy-lpc2106.asm
. The file riscy-lpc2106.asm
is the one actually 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.
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 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 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.
It might be convenient to use the GNU debugger in a few cases to trace through and correct 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
.
Because the version of GDB included with Insight
is on old version,
the bundle of tools includes just the newer GDB. It works well from
within Emacs, so we don't need Insight
.
The host-side of Riscy Pygness is written in Tcl. See riscy.tcl
.
We use a recent version of Tclkit for a 32-bit i386 Linux. It is
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 a Microsoft OS. 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 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.
lpc21isp
is included in the bundle of tools.
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:
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/.
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 flash
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).
http://www.blisstonia.com/software/lpc_prog_20060709.tgz by Edwin Olson, license GPL, per his posting to the<EMAIL_ADDRESS>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
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.s
xxx.s
to produce xxx.o
xxx.o
to produce xxx.elf
(and produce a list of symbols)
xxx.elf
to produce xxx.bin
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.
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.
;;
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, when typing interactively, 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.  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, it helps to have a certain familiarity with computers, Linux, and the command line. 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.
 (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).
See terminal
above.
See terminal
above.
See terminal
above.
To list the contents of a directory (in a terminal), type
ls -als
To see the various options for the ls
command, type
man ls
I usually set up a dir
alias so that when I type dir
, what gets
executed is ls -als
. Such aliases are typically set up by editing
your ~/.bashrc
file, but they can be set up on the fly at a command
prompt, e.g.,
alias dir='ls -als'
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.
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:
unzip xyz.zip
tar -xvf xyz.tar
tar -xzvf xyz.tar.gz
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.
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 could 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.
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 /
).
This is your ARM board
This is your Linux desktop (or laptop or whatever) computer.
The typical Linux computer is filled with documentation.
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 ls
info
command, e.g., info emacs
apropos
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.
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".
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 within Emacs).
C-h
C-ht
RET
M-x
Download a copy of Ubuntu 10.04.1 (the 32-bit i386 desktop version)
available from http://ubuntu.com. Save this to disk. It is a CD
image file named something like ubuntu-10.04.1-desktop-i386.iso
.
Write that CD image file to a blank CD-R disc to create a bootable CD.
Boot a PC with the Ubuntu Linux installation CD. You may need to alter your computer's set up settings to allow booting from a CD. Some computers, such as HP, allow you to bring up a boot menu by pressing Esc or whatever as you power up the computer.
Plug a flash USB stick into your PC.
While running Ubuntu, click on the "System" menu at the top of the screen. Click on "Administration". Click on "Startup Disk Creator".
You will see a window titled "Make Startup Disk". In the "Source disc
image (.iso) or CD:" field, choose the CD you booted from, or else
choose the CD image file (ubuntu-10.04.1-desktop-i386.iso
).
In the "Disk to use:" field, select the flash USB stick. If you have more than one USB disk connected, be sure to choose the correct one. Click on the "Erase Disk" button.
Under the "Stored in reserved extra space" radio button, slide the "How much:" slider to as reasonable size. I would give it everything over 1G. So, if you are using a 2G USB stick, give it 1G. If you are using a 4G USB stick, give it 3G. Then click the "Make Startup Disk" button.
When it finishes, you should have a USB stick that can be used to boot the computer. Furthermore, it is a USB stick which will save your settings and added software. It becomes an entire Riscy Pygness development system that you can carry on your key chain (of course it needs a real computer, if only temporarily, to do anything useful).
You only need to do this step once, so borrow a PC if yours does not have a CD drive.
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
JTAG cable
JRST
BSL
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)
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 parts 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 run 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:
A Forth image that runs on a target board (with an ARM CPU).
The image can be thought of as having 3 pieces:
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.
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.  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.  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] # The 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)
If you don't yet have a .emacs
file, copy the example file to your
home directory. It already contains the above lines.
$ cp ~/riscy/.emacs-example ~/.emacs $ cp ~/riscy/forthblocks.el ~/
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
),
VARIABLE
tags the following word as a variable.
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
.
If you installed the bundle of tools from the Riscy Pygness website (http://pygmy.utoh.org/riscy/arm-toolchain.tar.bz2), then you can ignore this section.
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.
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-<IP_ADDRESS>.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-<IP_ADDRESS>.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/
Here are some notes about generating the bundle of tools
(http://pygmy.utoh.org/riscy/arm-toolchain.tar.bz2). I used Ubuntu
10.04.1 which comes with make
and gcc
but we need to install the
texinfo
and libncurses-dev
packages for the compiles to succeed.
I downloaded the source from http://ftp.gnu.org/gnu/binutils/.
# apt-get install texinfo # cd /tmp # mkdir binutils-build # cd binutils-build # make /usr/local/src/binutils-2.20.1/configure --target=arm-elf --prefix=/usr/local/arm # make # make install
The most recent version appears to be version 7.2 of 2 Sept 2010. If we want Insight, then we get an older version of GDB (6.8). I chose to build just the newer GDB and forget about Insight. If we run GDB, we will do so from within Emacs.
I downloaded the source from http://ftp.gnu.org/gnu/gdb/gdb-7.2.tar.bz2.
# apt-get install libncurses-dev # cd /usr/local/src # gpg --verify gdb-7.2.tar.bz2.sig gdb-7.2.tar.bz2 # tar -xjf gdb-7.2.tar.bz2 # cd /tmp # mkdir gdb-build # cd gdb-build # /usr/local/src/gdb-7.2/configure --target=arm-elf --prefix=/usr/local/arm # make # make install
I downloaded lpc21isp-1.79.tar.gz into /usr/local/src/
and then
compiled it and linked to a shorter name. The process was
approximately the following (I don't think it needed a configure or
install step, but I might have misremembered).
# cd /usr/local/src/ # tar -xzvf lpc21isp-1.79.tar.gz # cd lpc21i* # make # cp lpc21isp-1.79 /usr/local/bin # ln -s /usr/local/bin/lpc21isp-1.79 /usr/local/bin/lpc21isp
See also the notes here at *Compiling lpc21isp.
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?  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 is what should appear
Welcome to Riscy Pygness Loading the dictionary from kernel-lpc2103.dictionary >
A 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.
Riscy Pygness should be easy to port to any ARM processor (ARM 7, ARM9). So far, I have run it on the 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 ARM7 processors should be minimal. It has not yet been ported to the ARM Cortex.
Riscy Pygness currently works on at least the following ARM CPU and board variants.
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 ARM 7 or ARM 9 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.
The main task will be to adjust for differences in how I/O and peripherals are handled, setting the clock speed, noting which pin (if any) connects to an LED, etc.
Email<EMAIL_ADDRESS>Home page: http://pygmy.utoh.org
Main Riscy Pygness page: http://pygmy.utoh.org/riscy
Date: 2010-11-10 Wed
HTML generated by org-mode 6.21b in emacs 23


This is a fairly detailed description of Bounty X. Our app is the best thing since sliced bread basically. You should probably go and get it immediately, you might even see your favourite celebrity on there! This description will be boxed and will look fancy!
Interested? Register for updates and get notified when it releases!


Collapse Anything
With the new generation of collapse integrated in Sticky 3.0 you can make absolutely any element on the page act like a collapse / dropdown menu.
Tap to Show more ContentBecause your feedback is very important to us, and we always listen! Using collapse you can create any dropdown hidden element like this one and activate it with a simple tap! Great for a lot of elements.
Collapse Lists
Great when you want to include a simple menu or multiple options in your page and don't want to overcomplicate the experience.
Colored Collapse
Simple link list that can get tapped to reveal more content. We used links in our example, you can use anything you want.


A client object used to interact with different Google Data APIs.
- A
- C
- D
- G
- M
- N
- P
| [RW] | auth_handler | A subclass of GData::Auth that handles authentication signing. |
| [RW] | http_service | A subclass of GData::HTTP that handles making HTTP requests. |
| [RW] | headers | Headers to include in every request. |
| [RW] | version | The API version being used. |
| [RW] | clientlogin_url | The default URL for ClientLogin. |
| [RW] | clientlogin_service | A default service name for ClientLogin (overriden by subclasses). |
| [RW] | authsub_scope | The broadest AuthSub scope for working with an API. This is overriden by the service-specific subclasses. |
| [RW] | source | A short string identifying the current application. |
Source: show
# File lib/gdata/client/base.rb, line 39 def initialize(options = {}) options.each do |key, value| self.send("#{key}=", value) end @headers ||= {} @http_service ||= GData::HTTP::DefaultService @version ||= '2' @source ||= 'AnonymousApp' end
Sets a private key to use with AuthSub requests.
Source: show
# File lib/gdata/client/base.rb, line 173 def authsub_private_key=(key) if @auth_handler.class == GData::Auth::AuthSub @auth_handler.private_key = key else raise Error, "An AuthSub token must be set first." end end
Sets an AuthSub token for the service.
Source: show
# File lib/gdata/client/base.rb, line 168 def authsub_token=(token) self.auth_handler = GData::Auth::AuthSub.new(token) end
Source: show
# File lib/gdata/client/base.rb, line 159 def authsub_url(next_url, secure = false, session = true, domain = nil, scope = nil) if scope.nil? scope = @authsub_scope end GData::Auth::AuthSub.get_url(next_url, scope, secure, session, domain) end
Performs ClientLogin for the service. See GData::Auth::ClientLogin for details.
Source: show
# File lib/gdata/client/base.rb, line 145 def clientlogin(username, password, captcha_token = nil, captcha_answer = nil, service = nil, account_type = nil) if service.nil? service = @clientlogin_service end options = { :account_type => account_type } self.auth_handler = GData::Auth::ClientLogin.new(service, options) if @clientlogin_url @auth_handler.auth_url = @clientlogin_url end source = GData::Auth::SOURCE_LIB_STRING + @source @auth_handler.get_token(username, password, source, captcha_token, captcha_answer) end
Performs an HTTP DELETE against the API.
Source: show
# File lib/gdata/client/base.rb, line 127 def delete(url) return self.make_request(:delete, url) end
Performs an HTTP GET against the API.
Source: show
# File lib/gdata/client/base.rb, line 102 def get(url) return self.make_request(:get, url) end
Sends an HTTP request with the given file as a stream
Source: show
# File lib/gdata/client/base.rb, line 51 def make_file_request(method, url, file_path, mime_type, entry = nil) if not File.readable?(file_path) raise ArgumentError, "File #{file_path} is not readable." end file = File.open(file_path, 'rb') @headers['Slug'] = File.basename(file_path) if entry @headers['MIME-Version'] = '1.0' body = GData::HTTP::MimeBody.new(entry, file, mime_type) @headers['Content-Type'] = body.content_type response = self.make_request(method, url, body) else @headers['Content-Type'] = mime_type response = self.make_request(method, url, file) end file.close return response end
Sends an HTTP request and return the response.
Source: show
# File lib/gdata/client/base.rb, line 71 def make_request(method, url, body = '') headers = self.prepare_headers request = GData::HTTP::Request.new(url, :headers => headers, :method => method, :body => body) if @auth_handler and @auth_handler.respond_to?(:sign_request!) @auth_handler.sign_request!(request) end service = http_service.new response = service.make_request(request) case response.status_code when 200, 201, 302 #Do nothing, it's a success. when 401, 403 raise AuthorizationError.new(response) when 400 raise BadRequestError.new(response) when 409 raise VersionConflictError.new(response) when 500 raise ServerError.new(response) else raise UnknownError.new(response) end return response end
Performs an HTTP POST against the API.
Source: show
# File lib/gdata/client/base.rb, line 117 def post(url, body) return self.make_request(:post, url, body) end
Performs an HTTP POST with the given file
Source: show
# File lib/gdata/client/base.rb, line 122 def post_file(url, file_path, mime_type, entry = nil) return self.make_file_request(:post, url, file_path, mime_type, entry) end
Constructs some necessary headers for every request.
Source: show
# File lib/gdata/client/base.rb, line 132 def prepare_headers headers = @headers headers['GData-Version'] = @version headers['User-Agent'] = GData::Auth::SOURCE_LIB_STRING + @source # by default we assume we are sending Atom entries if not headers.has_key?('Content-Type') headers['Content-Type'] = 'application/atom+xml' end return headers end