amforth
amforth
Release 5.3
Matthias Trute
1 User’s Manual 1
1.1 User’s Manual For Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 User’s Manual for Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Instructions for Building amforth-5-1 using Atmel Studio 6.1 Components . . . . . . . . . . . . 12
2 FAQ 15
2.1 Where do I find more information? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 How do I start with amforth? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3 How do I use amforth interactively? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.4 There are no hexfiles in the distribution archive! . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5 I get no serial prompt! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.6 What do all the words do? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.7 Can I embed amforth into other programs? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.8 Can I use code written in C (or any other language) with/in amforth? . . . . . . . . . . . . . . . 16
2.9 How do I send forth code to the system? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.10 I found a bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.11 Does amforth run on hardware xy? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.12 What about the fuses? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.13 What about boot loaders? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.14 What do I need for linux? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.15 How do I use Atmel’s assembler with linux? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.16 What resources are available in my own assembly words? . . . . . . . . . . . . . . . . . . . . . 18
3 Technical Guide 19
3.1 First Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.2 Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.3 Source Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.4 Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.5 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.6 Standard Wordlists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.7 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4 Cookbook 43
4.1 Popular Boards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.2 Hardware Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.3 General Code Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.4 Programming and Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
i
5.7 Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.8 Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.9 Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.10 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.11 Extended VM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.12 Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5.13 Interrupt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5.14 Logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5.15 MCU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
5.16 Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
5.17 Multitasking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.18 Numeric IO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.19 Search Order . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
5.20 Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
5.21 String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.22 System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.23 System Value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
5.24 System Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
5.25 Systemm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
5.26 Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
5.27 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
5.28 Tools Ext (2012) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
5.29 unclassified . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6 History 113
6.1 5.4.2013: release 5.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.2 27.12.2012: release 5.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.3 27.7.2012: release 4.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.4 26.3.2012: release 4.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.5 4.2.2012: release 4.7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
6.6 6.10.2011: release 4.6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
6.7 29.6.2011: release 4.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
6.8 24.5.2011: release 4.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.9 1.5.2011: release 4.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.10 19.9.2010: release 4.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.11 2.9.2010: release 4.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
6.12 1.7.2010: release 4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
6.13 25.5.2010: release 3.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
6.14 25.4.2010: release 3.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
6.15 24.1.2010: release 3.7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.16 1.10.2009: release 3.6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.17 1.9.2009: release 3.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.18 11.4.2009: release 3.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.19 22.2.2009: release 3.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.20 10.1.2009: release 3.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.21 10.11.2008: release 3.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.22 17.10.2008: release 3.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.23 1.8.2008: release 2.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.24 27.6.2008: release 2.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.25 5.4.2007: release 2.7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.26 27.1.2008: release 2.6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.27 6.12.2007: release 2.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.28 11.10.2007: release 2.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.29 29.7.2007: release 2.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.30 17.6.2007: release 2.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.31 22.5.2007 release 2.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.32 2.5.2007 release 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.33 25.4.2007 release 1.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
ii
6.34 10.4.2007 release 1.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.35 3.4.2007 release 1.7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
6.36 25.3.2007 release 1.6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
6.37 14.3.2007 release 1.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
6.38 5.3.2007 release 1.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.39 24.2.2007 release 1.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.40 3.2.2007 release 1.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.41 20.1.2007 release 1.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.42 4.1.2007 release 1.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.43 17.12.2006 release 0.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.44 7.12.2006 release 0.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.45 24.11.2006 release 0.7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.46 20.11.2006 release 0.6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.47 13.11.2006 release 0.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.48 5.11.2006 release 0.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.49 31.10.2006 release 0.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.50 27.10.2006 release 0.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.51 16.10.2006 release 0.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
iii
iv
CHAPTER
ONE
USER’S MANUAL
This guide makes a few assumtions. Your linux should be a fairly recent linux distribution. For this document an
Ubuntu 12.04 LTS is used, others should work in a similiar way.
First you’ll have to install some packages with the package manager:
• wine (any version)
• ant or make (any version)
• avrdude
They may need quite a lot more packages, install all of them.
Next download the amforth package and un-tar (or unzip) it into a new, empty folder:
> pwd
.../amforth
> ls
> tar xvf amforth-x.y.tgz
.. lots of files
> ls
appl core doc examples lib LICENSE.txt readme.txt
tools
> mkdir Atmel
>
Now you need access to an installed Atmel Studio 6 installation. Locate the program directory and copy the file
avrasm2.exe and the whole Appnotes2 directory into a newly created directory called Atmel:
> ls Atmel
avrasm2.exe Appnotes2/
>
The Appnotes2 directory contains a lot of inc files. They are text files. There is no need to convert them from
DOS to unix text format. Take them as they are.
1.1.2 Testing
To test if the installation is complete, change into the directory appl/template. There run either make or ant
with the target name template.hex to test the assembler setup.
> make template.hex
wine ../../Atmel/avrasm2.exe -I ../../Atmel/Appnotes2
-I ../../core -I ../../core/devices/atmega1284p -fI
1
AmForth Documentation, Release 5.3
Ant works similiar, note the warning at startup, it can safely ignored:
> ant template.hex
Unable to locate tools.jar. Expected to find it in
/usr/lib/jvm/java-6-openjdk-amd64/lib/tools.jar
Buildfile: ....amforth/appl/template/build.xml
template.hex:
[echo] Producing Hexfiles for atmega128
BUILD SUCCESSFUL
Total time: 4 seconds
>
After this step, there should be a number of new files in the directory:
> ls
build.xml dict_appl.inc template.asm
template.hex template.map dict_appl_core.inc
makefile template.eep.hex template.lst words
If something went wrong, read the error messages, fix them and repeat this step until all is well.
If everything works fine, it is now possible to start your own project. This as simple as making a copy of the
template directory and editing a few files there.
> pwd
... amforth/appl
> cp -r template my
> cd my
>
Now edit the files template.asm and makefile (or build.xml if you use ant). The file template.asm
has a lot of settings, to get a quick start only the lines
.equ BAUD = 9600
.include "drivers/usart_0.asm"
may need to be changed. The baud number should be obvious. The line usart_x.asm defines the usart port of
the controller on which the command prompt will be available. There are only real usart ports available, no USB
devices (this may change in future releases..)
In the makefile find the lines
# set the fuses according to your MCU
LFUSE=0xnn
HFUSE=0xnn
# some MCU have this one, see write-fuses target below
EFUSE=0xnn
lfuse="0xc6"
/>
</target>
and change the fuses to meet you hardware settings. Be careful with these numbers, they can potentially corrupt
your controller cpu beyond repair.
The next essential setting is the controller itself
# the MCU should be identical to the device
MCU=atmega1284p
in the build.xml find and change all occurances that look like
mcu="atmega1284p"
with the proper name. The mcu names are taken verbatim as file names in the Atmel/Appnotes2 directory and
as directory names in the core/devices directory. Case is significant (should be almost always lower case).
With these changes, rebuild the hex files as described above.
The last and final step is to transfer the hex files to the controller. The build tools use the program avrdude. To
get the hex files to the controller a special hardware called programmer is needed. There are many different ones
available, ranging from simple parallelport tools like the STK200 to expensive tools like the Atmel JTAG ICE
MK2. Dont start trying to use exotic tools like ponyser or other self-made el-cheapo tools unless you know what
you’re doing.
The Atmel tools AVR ISP MK2 and Dragon are not that expensive and work with the USB port of your computer.
Linux needs a file named /etc/udev/rules.d/99-atmel.rules to make them accessible for users:
# Atmel AVR ISP mkII
SUBSYSTEM=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2104", GROUP="users", MODE="0660"
# usbprog bootloader
ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c62", GROUP="users", MODE="0660"
# USBasp programmer
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", GROUP="users", MODE="0660"
# USBtiny programmer
ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c9f", GROUP="users", MODE="0660"
Note, that the correct GROUP name should include one of the groups your linux account is a member of:
> id
uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),
27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),125(libvirtd)
Here the GROUP name “users” would not work! But “user” or “plugdev” would do. If you do not have a setup
like this, only root can access the programmer. If you want to use the parallelport programmer STK200, your
account should be a member of the “lp” group (check with ls -l /dev/parport*).
Any changes to the rules file are detected almost immediately, there should be no reason to restart any linux
program.
Project Setup
If your hardware setup is finished, you need to edit the makefile or build.xml to reflect the settings. In the
makefile find and edit the lines
The build.xml is different. This file uses a property file named programmer.properties to set the
name and the port of the programmer hardware. The build.xml file uses a substring from the label to define
the programmer. If you want to use e.g. the AVR Dragon as the programmer, just use the name “dragon” as
programmer idenifier in your build.xml. The ant utility will expand this to “avr.programmer.<label>port”
for the -P parameter and “avr.programmer.<label>” to the -c parameter to generate the right command line for
avrdude from the property file.
Serial programmers may be difficult while getting the right port name if using RS232-to-USB converters. The
mapping may change over time (e.g. every reboot or USB bus reset).
If everything goes ok, the final command make template should re-generate the hex files and transfer them to the
controller. The default program output should be verbose enough to track any error messages.
1.2.1 Introduction
This manual describes the amforth programming language and provides details on how to customize the standard
release for use on your target platform. This document focuses on developing amforth applications in the Windows
environment, using Atmel’s freeware AVRStudio4. For information on developing amforth applications in the
Linux/Unix environment, consult the Web.
amforth is a variant of the ans94 Forth language, designed for the AVR ATmega family of microcontrollers
(MCUs). amforth v2.3 was developed and maintained by Matthias Trute; the amforth project is hosted and main-
tained on SourceForge.net (http://sourceforge.net/projects/amforth).
You create your amforth application by creating a custom template file, (optionally) modifying some of the files
included in the distribution set, assembling them with AVRStudio4, then moving the resulting object file into flash
in the target hardware. Your amforth application resides in flash, using very little RAM or EEPROM.
amforth is fully interactive. You can connect your target hardware to a serial port on your host PC, run a comm
program such as Hyperterm, and develop additional Forth words and applications on the target. Each word you
create interactively is also stored in flash and is available following reset or power-cycle of the target. You can
select one of your amforth words to be the boot task, which will run automatically on the next reset or power-cycle.
You can also write interrupt service routines (ISRs) in amforth for handling low-level, time-critical events.
This manual assumes familiarity with Atmel’s AVRStudio4 development suite (Windows OS);
some knowledge of AVR assembly language programming is helpful but not necessary for ba-
sic use of the amforth language. You can download the free AVRStudio4 suite from Atmel
(http://www.atmel.com/dyn/products/tools_card.asp?tool_id=2725). You can use version 4.2 or higher;
note that you need to fill out a fairly simple registration page to complete the download.
This manual also assumes a working knowledge of the Forth programming language. If you are new to Forth, or
if you need a quick refresher or a reference page, you can find a full Web version of Leo Brodie’s marvelous book,
“Starting Forth,” online at http://home.iae.nl/users/mhx/sf.html.
I created this document to support Matthias’ work on amforth. He has designed an excellent Forth that I enjoy
using, but I thought newcomers to amforth could use a bit more detailed explanation on setting up an application.
amforth v4.2 is available as a single downloadable file (amforth-4.2.zip). Download this file into your
working folder (I’ll use c:\projects\amforth-4.2 throughout this document) and unzip them, preserving
the subdirectory structure. You should end up with the following subdirectory layout:
c:\projects\amforth-4.2\ <-- main directory, holds your project files
\appl <-- holds amforth systems for various MCUs
\core <-- holds source for amforth primitives
\doc <-- holds amforth documentation
\examples <-- holds small projects written in amforth
\lib <-- holds libraries of Forth source files
\tools <-- holds amforth support tools (pd2amforth)
You will develop your custom applications by creating simple assembly language source files in the main directory,
assembling your source files and the amforth source files with AVRStudio4, then downloading the resulting .hex
and .eep files into your target with AVRStudio4.
Note that although you will be creating assembly language source files, the files will be little more than a few macro
invocations. Generally, you will not need to know any AVR assembly language to develop your applications.
The following sections describe the various subdirectories and files found in the initial installation of amforth.
core\words subdirectory
The core\words subdirectory holds a large collection of amforth words, each defined in a separate .asm file.
Each file in this subdirectory is a complete word definition, ready to assemble into the final application. For
example, here is the entire contents of equal.asm, the source file for the word =, which compares two values on
the top of the stack and leaves behind a flag that is TRUE if the values match or FALSE if they don’t.
; ( n1 n2 -- flag ) Compare
; R( -- )
; compares two values
VE_EQUAL:
.dw $ff01
.db "=",0
.dw VE_HEAD
.set VE_HEAD = VE_EQUAL
XT_EQUAL:
.dw PFA_EQUAL
PFA_EQUAL:
ld temp2, Y+
ld temp3, Y+
cp tosl, temp2
cpc tosh, temp3
PFA_EQUALDONE:
brne PFA_ZERO1
rjmp PFA_TRUE1
This source file gives an excellent view of the layout for each amforth word. It isn’t necessary that you understand
how a word is laid out inside the dictionary in order to use amforth. However, if you ever need to define your own
amforth words, you will need to follow the layout shown here to make sure your words properly integrate into the
dictionary.
core\devices subdirectory
The core\devices subdirectory holds several folders, each defining a target MCU. For each target MCU
defined, the associated folder holds four files..
The device.asm file is intended to be included as part of your application, and provides assembly language def-
initions for MCU-specific parameters, such as where RAM starts, the number and layout of the interrupt vectors,
and where high flash memory starts.
The <target>.frt file contains Forth source defining MCU-specific parameters, such as IO register names
and addresses. You can use this file when working in Forth on your target system; it is not needed when building
an amforth system in AVRStudio4.
The device.inc file contains assembler source and is intended to be included as part of any amforth applica-
tions you build for the target device. This file creates Forth words in your dictionary that provide access to the
MCU registers on your target.
(Comments on device.py file are needed.)
If you need to develop support files for a different Atmel MCU, you can copy the existing files for a similar device
and use these copies as a starting point for your efforts. Rename the files to match the target MCU, then refer to
the appropriate Atmel reference manual to determine the proper values for the various registers and parameters.
Where possible, ensure that the assembly language names for the ports match those in the existing .asm file. If
the names do not match, or if you need to add new names to provide support for a new subsystem (such as the
CANBus ports on an AT90CAN128), you may need to edit one or more existing amforth source files.
(Add details on how the device.py file is generated.)
core\amforth.asm
amforth.asm contains a small amount of assembly language source that: * defines the Forth inner interpreter,
* declares the starting address of the Forth kernel, * allocates the memory for the final system, * sets up the small
area of EEPROM used by the Forth system, * declares the interrupt service routine (ISR) support, * adds the
words to be assembled into low flash memory (dict_appl.inc), * adds the words to be assembled into high
flash memory (dict_appl_core..inc)
The amforth.asm included in your original download is essentially complete, in that when assembled it will
create most of an amforth system. However, key information about the target hardware is missing and must be
supplied by you in what is known as a template file. Details on what this template file contains and how you use
the template file to describe your target hardware are contained in a later section.
Note that amforth.asm refers to a turnkey application (see XT_APPLTURNKEY in the .eseg segment). This
execution token is assumed to be an amforth word defined somewhere in your application. The default turnkey
application, defined in the file applturnkey.asm, provides a typical Forth interactive environment. You can
customize applturnkey.asm as needed; see the section below on applturnkey.asm.
core\dict_core.inc
This file lists all amforth words that will be included in that part of amforth’s dictionary stored in high flash memory
(also known as the bootloader area or NRWW flash). The following excerpt from the standard dict_core.inc
file shows how the file fits into the amforth system.
; this part of the dictionay has to fit into the nrww flash
; section together with the forth inner interpreter
.include "words/int-on.asm"
.include "words/int-off.asm"
.include "words/int-restore.asm"
.include "words/exit.asm"
.include "words/execute.asm"
.include "words/dobranch.asm"
.include "words/docondbranch.asm"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.include "words/doliteral.asm"
.include "words/dovariable.asm"
.include "words/doconstant.asm"
.include "words/douser.asm"
.include "words/fetch.asm"
.include "words/store.asm"
.include "words/cstore.asm"
.include "words/cfetch.asm"
Each line in dict_core.inc is either a comment or an .include statement that adds an assembly language
source file from the words/ folder. Thus, the words listed in this file and the order in which they appear define
the high-memory portion of your amforth project dictionary.
In general, the words in dict_core.inc provide flash read and write capability, though other primitives, such as
branch operations and the inner interpreter, may also be included. The amforth design puts these flash read/write
operations in high flash memory because the AVR MCU does not allow instructions in one area of flash memory
to modify that same area of flash memory. Putting the flash read/write primitives in high flash memory allows
amforth words in low flash memory to use these primitives to modify the dictionary, which is kept in low flash.
The amount of high flash memory varies based on the type of AVR device. Some small devices may have so little
high flash memory that you won’t be able to fit all of the words in this file into the available memory. Should this
happen, you may be able to move some of the .include statements from this file into dict_minimum.inc (see
below). Note, however, that this must be done carefully, to keep the flash read/write words (at least) in high flash
memory.
For nearly all applications, you can simply leave dict_core.inc unchanged.
core\dict_minimum.inc
This file was called dict_low.inc in previous versions of amforth. Like dict_core.inc, it contains a
series of .include statements that add a list of core amforth words to the final system.
The words included in dict_minimum.inc are stored in the target system in low flash memory. The entire
dictionary in low flash memory that is created when you build an amforth system becomes your application’s
dictionary. The last word in dict_minimum.inc will be the last word created in your application, assuming
you don’t add any other words as you build your application.
You could, if you chose, edit dict_minimum.inc to change the order or content of your application’s dictio-
nary. However, this really should be avoided. Instead, use dict_minimum.inc as provided and add your own
custom .include file that defines your application. That file, named dict_appl.inc, is discussed below.
The following sections describe the files you need to create for building your own amforth system. In general,
these are the only files you will need to edit during development. You can often find suitable files already in the
distribution that can serve as starting points for these files; just copy and edit them to make your own files.
dict_appl.inc
The file dict_appl.inc is created by you and contains those amforth words that you want to add to your
application above and beyond the words added by dict_minimum.inc and dict_core.inc.
You create your own dict_appl.inc file using a standard text editor, such as AVRStudio4’s text editor. Here
is a simple dict_appl.inc file that I use for creating a typical amforth development system. It provides words
for printing unsigned numbers, listing the dictionary, and displaying parts of memory.
Here is the dict_appl.inc file I created for my sample amforth system.
; this dictionary contains optional words
; they may be moved to the core dictionary if needed
.include "words/dotstring.asm"
.include "words/squote.asm"
.include "words/words.asm"
.include "words/edefer.asm"
.include "words/rdefer.asm"
.include "words/is.asm"
.include "applturnkey.asm"
.include "words/int-store.asm"
.include "words/1ms.asm"
.include "words/ms.asm"
.include "dict_compiler.inc"
.include "words/show-wordlist.asm"
.include "devices/atmega328p/device.inc" ; <-- hard-coded path!
.include "dict_usart.inc"
Note that the first statement in my file includes the dict_minimum.inc file. Note also that I have hard-coded
the path to the appropriate device.inc file. If you build your application for a different MCU, you will need
to adjust this path.
You are free to include whatever words from the words/ folder you want in your application. Note that if you
accidentally include a duplicate word, assembling the resulting application will generate an error; you will need
to edit out the duplicate word and reassemble.
dict_appl_core.inc
The file dict_appl.inc is created by you and contains those amforth words that you want to add to your
application above and beyond the words added by dict_minimum.inc and dict_core.inc. The difference
between this file and dict_appl.inc above is the words in this file will be written to high flash (the NRWW
or bootloader section).
Because of the limited amount of space in the NRWW sections of most ATmega devices, you will want to limit
the words in this file to those absolutely required to support amforth. In general, these are words that modify flash,
such as istore.asm. Below is the dict_appl_core.inc file for my sample amforth system.
.include "dict_core.inc" ; <-- required!
.include "words/estore.asm"
.include "words/efetch.asm"
.include "words/istore.asm"
.include "words/istore_nrww.asm"
.include "words/ifetch.asm"
Note that my file starts by including the dict_core.inc file. You can add additional words in your
dict_appl_core.inc, subject to space limitations in your MCU. If you accidentally include words already
included by other segments of the amforth system, you will get assembler errors for the duplicates; just remove
the duplicates from your dict_appl_core.inc file.
applturnkey.asm
This file is created by you and contains the assembly language source for a turnkey application, as called out in
the file amforth.asm (see appropriate section above).
The file applturnkey.asm usually contains amforth words, written in assembly language, in the form shown
in the words/ subdirectory description above.
The following sample applturnkey.asm file shows the default turnkey application. This application is suitable
for most amforth systems and should be included when you build your system. Here is the applturnkey.asm
file for my sample amforth system.
; ( -- ) System
; R( -- )
; application specific turnkey action
VE_APPLTURNKEY:
.dw $ff0b
.db "applturnkey",0
.dw VE_HEAD
.set VE_HEAD = VE_APPLTURNKEY
XT_APPLTURNKEY:
.dw DO_COLON
PFA_APPLTURNKEY:
.dw XT_INITUSER
.dw XT_USART
.dw XT_INTON
.dw XT_CR
.dw XT_CR
.dw XT_VER
.dw XT_CR
.dw XT_SLITERAL
.dw 22
.db "Karl’s amForth system "
.dw XT_ITYPE
.dw XT_EXIT
The above simple application initializes the user area and USART0, displays amforth’s version information on the
serial terminal, displays an extra message proclaiming this as my amforth system, then exits to the main amforth
shell.
Note that my custom message ends with a space. I did this so the message had an even number of letters. If the
string length is odd, the assembler reports a warning that it had to add an zero-byte to pad out an even number of
bytes. (I could have also used the ,0 technique shown in the declaration of the applturnkey string at the top of the
file.)
You define the characteristics of your target hardware and your application in a template file. The template file is
an assembly language source file you create with a standard ASCII text editor, such as AVRStudio4’s text editor.
Although this is called an assembly language source file, you will typically not write any true assembly language
instructions. Instead, the contents of your template file will largely consist of .include and .equ statements.
Here is the template file, myproj.asm, for defining my sample amforth system running on an Atmega328P
MCU with a clock frequency of 16 MHz.
;
; myproj.asm a simple AVRStudio4 assembly-language project for amforth
;
;
; The order of the entries (esp the include order) must not be
; changed since it is very important that the settings are in the
; right order
;
; first is to include the macros from the amforth
; directory
.include "macros.asm"
; terminal settings
.set WANT_ISR_RX = 1 ; interrupt driven receive
.set WANT_ISR_TX = 0 ; send slowly but with less code space
.if WANT_ISR_RX == 1
.set USART_B_VALUE = (1<<TXEN0) | (1<<RXEN0)| (1<<RXCIE0)
.else
.set USART_B_VALUE = (1<<TXEN0) | (1<<RXEN0)
.endif
.equ want_fun = 1 ; in case of an error out print an additional line with an caret indicating the
Your template file must include the file macros.asm, and this should be the first statement (except for comments,
of course) in your template file. The file macros.asm contains a set of macros specific to amforth, used to
simplify the coding of the amforth words and underlying assembly language routines.
Next, your template file should include the device.asm file specific to your target hardware. This file is found
in the core\devices\<target> folder that matches your target hardware. Note that in the above sample,
the path to the device.asm file is hard-coded in the .include statement. If you are using a different MCU, you
will need to edit this .include statement.
Next, your template file should declare a key equate required by amforth. The equate F_CPU defines the oscillator
frequency, in Hertz, of your target hardware and is expressed as a long integer. The example shown above shows
Begin by creating a new assembly-language project in AVRStudio4. For my sample, I named this project myproj
and created it in the c:\projects\amforth-4.2 folder.
I then created the four above files in my project folder (the folder holding myproj.aps).
Next, I added the path to the core\ folder to my project. To do this, click on Project/Assembler Options and
locate the entry for Additional include path. Enter in this field the full path to the corefolder; in my case, this path
is C:\projects\amforth-4.2\core.
This completes setup in AVRStudio4. Pressing F7 assembles the source files and leaves behind two object files.
The file myproj.hex contains the flash contents for my amforth system and the file myproj.eep holds the
EEPROM values needed at startup.
I then hooked my programming pod (an AVRISP mkII) to the target board and applied power. I first made sure that
the device’s fuses were properly set. In particular, I checked that the bootloader area was set to the maximum (2048
words for the ATmega328P), the EESAVE fuse was checked, and the fuse for BOOTRST (jump to bootloader on
reset) was NOT checked.
Finally, I downloaded the EEPROM file to the target, followed by downloading the flash file. When I reset the
target, the Hyperterm window hooked to the target’s serial port showed the expected amforth announcement. My
amforth system was up and running!
When you build a properly created amforth system, you should not get any errors or warnings. If the assembler
complains about not finding included files, double-check the layout of your amforth folder; it should match what
I’ve described at the beginning of this manual. Additionally, make sure you entered the path to your core\ folder
correctly in the Additional include path box when you set up the project’s properties.
If the project builds properly but does not provide characters to your serial port when you download your code to
the target, double-check that your Hyperterm settings match those in your template file (myproj.asm above).
Also confirm that you got the MCU frequency correct in your template file. Also double-check that you put the
correct hard-coded paths in your template file and in your dict_appl.inc file. Also make sure you download
BOTH the .hex and the .eep files for your project, and that you have the EESAVE fuse on your device checked.
The above method uses two hard-coded paths in your source files. This can cause problems if you later try to
change processors; it’s easy to forget to change one of the hard-coded paths and it can take time to track down the
error.
As an alternative, you can go to Project/Assembler Options and remove the path you added in the Additional
include path field. Edit the Additional parameters field to include the following text, all entered on a single line:
-I C:\projects\amforth-4.2\core -I C:\projects\amforth-4.2\core\devices\atmega328p
Note that the above entry is for my project; adjust as needed for your file paths and target MCU. Be sure to include
the single space after each -I as shown!
With this change in place, you can now remove the hard-coded paths in your template file and your
dict_appl.inc file. For example, in my myproj.asm template file above, I had the line:
.include "devices/atmega328p/device.asm" ; <-- hard-coded path!
After adding the above change to the Additional parameters field, this line becomes:
.include "device.asm"
1.3.1 Motivation
Building AmForth requires a compatible assembler. Atmel Studio 6.1 for Windows includes avrasm2.exe which
works great for this process but using Atmel Studio is overkill in my opinion. On my Mac Atmel Studio 6.1 takes
forever to load and execute (using the Parallel’s Desktop) plus it crashes half the time when I try to make changes
to my amforth project’s configuration.
I wanted to be able to quickly make changes to AmForth and turn around new hex and eep files for downloading
into an Arduino Uno. Note: the technique I discuss here can be used for any Atmel target hardware, not just an
Arduino Uno.
1.3.2 Prerequisites
1. Download and installation of the free version of Atmel Studio 6.1 on a Windows computer
2. Being comfortable running a command prompt in the Windows environment
1.3.3 Process
1. Create a project directory into which we are going to copy a bunch of files. I chose c:\amforth for my
project directory.
2. Copy avrasm2.exe and the complete include directory from c:\Program Files\Atmel\Atmel
Toolchain\AVR Assembler\Native\2.1.39.1005\avrassembler into the project direc-
tory
3. Uncompress and un tar the amforth-5.1 distribution file (amforth-5.1.tar.gz) into the project direc-
tory
4. Go into the amforth-5.1\appl\arduino directory of the distribution and copy uno.asm,
dict_appl_code.inc, dict_appl.inc and the words directory into the project directory.
5. Go into the amforth-5.1\core\devices directory and find the directory named with the proces-
sor you are going to use (in my case atmega328p) and from that directory copy device.asm and
device.inc into the project directory.
6. Create a bat file in the project directory with the following content:
REM batch file for assembling amforth on windows
avrasm2.exe -fI -o uno.hex -e uno.eep -l uno.lst -I .\ -I amforth-5.1\core -I include -v0 uno.asm
If all is well, change directory to your project directory and type make from a command prompt. In less than a
second you will have the new amforth files (hex file and eep file) for programming into you target hardware. You
could now uninstall Atmel Studio if you want as it is no longer required.
Making changes to amforth is now very easy and turn around is very fast.
1.3. Instructions for Building amforth-5-1 using Atmel Studio 6.1 Components 13
AmForth Documentation, Release 5.3
TWO
FAQ
There are 2 documentation files: a User’s Manual written by Karl Lunt and a more Technical Guide.
More can be found in the Maillinglist archive
First: You will have to build your own amforth first. To do this you really want to make copy of the
appl/template directory and edit the files in it to fit your environment: controller type, cpu frequency, serial
port settings etc. The files are well documented. Once the assembler produces two hex files and no errors (warn-
ings should not come up either) you can proceed. If you are using the Atmel studio, make sure that the project
settings include the generation of the eep files. This settings may be disabled by default.
Second you need a programmer to transfer the hex files you generated to the controller. The only programmers
that can be used are those that can work on bare (micro controller) systems: ISP (e.g. the Atmel AVRISPmk2 or
stk200 or ponyser), JTAG (e.g. the AVR Dragon), High Voltage programming (rarely used) or DebugWire (same:
rarely used). Programming tools that relies on a boot loader on the micro-controller itself can not load amforth
(the reasons are explained in the Technical Guide.
The program to talk with the programmer is avrdude. It is a swiss army knife like tool, that works for almost
all devices on all operating systems (Linux, Windows, MacOS and few more). The Makefiles / ANT files use it.
Other programs (just like the famous Atmel Studio) are never used by me, you are on your own.
After you transferred “burned” both hex files (one for the flash memory, one for the EEPROM memory), you can
begin working with amforth on the serial connection.
At the command prompt you can enter any command and can explore the controller. To simply add two numbers
just do the following:
> 24 42 + .
66 ok
>
To get the content of an IO register just use the memory mapped address (the example reads the 16bit return stack
pointer which the just the normal mcu stack pointer):
> $5d @ .
1101 ok
> rp@ .
1101 ok
>
15
AmForth Documentation, Release 5.3
8bit registers just use the c@ command instead of the @. Writing to any address is just as simple:
> 17 pad !
ok
> pad @ .
17 ok
>
Hex-files are very specific to the hardware, even the change of the oscillator frequency needs a rebuild. And every
processor wants its own settings. There would be far too many different hex-files. For some targets a hex-file is
provided (e.g AVR Butterfly).
You need to program two hex files, one for the flash memory and one for the EEPROM. The makefiles do that
already automatically.
Next check are the frequency settings. Atmegas need a configuration (fuse setting) to use an external clock
source. By default they run with an unstabilized 1MHz internal clock source, which is not well suited for serial
communication. Check the datasheet of your controller to find the correct fuse settings, they are different for
different atemgas and very sensitive, be absolutly careful! Rebuilt the hex files with the proper frequency (F_CPU
setting).
Finally check the terminal settings: default are 9600 8N1, no flow control. If your terminal has different settings,
change them.
Finally check the hardware. You may add a LED (or a scope) to the TX pin to check wether the controller sends
out the boot message upon reset. Plug off all programmers (they may keep the RESET pin).
Check the mailing list archive for other hints or (finally) ask there for help.
amforth tries to implement the ans94 dialect of forth. The last public version is available at (e.g.) Taygeta Archive
Embedding amforth into other programs (e.g. written in C) is almost impossible. Amforth is designed to run
stand-alone and does not follow any conventions that may be used on other systems.
2.8 Can I use code written in C (or any other language) with/in am-
forth?
16 Chapter 2. FAQ
AmForth Documentation, Release 5.3
Basically send them as ascii text via the terminal line. A command line like:
> ascii-xfr -s -c 10 -l 100 devices/atmega32.frt > /dev/ttyS0
can be used. amforth does not currently support any kind of flow control. Any transfer has to be slow enough to
not overrun the buffers. A more sophisticated approach is described in Use of the amforth-shell.py utility
amforth is targeted to Atmel AVR Atmega controllers. It does not and never will run on Attiny controllers or
on completely different architectures like PIC or 8051 etc. Work is currently under way to fully support Atmels
ATXMega’s.
Just set them to the factory defaults and adjust the oscillator settings only. amforth uses the self programming
capabilities so if any boot loader works, amforth should do so. Make sure that the boot loader size is as large as
the NRWW flash size, otherwise the flash write operation may fail silently and crash your system completely.
amforth overwrites them, they are no longer existent. And this can only be changed for boot loaders with an
application usable API to use the flash self programming feature. There are none currently available. With such
an API the only word that’s need to be rewritten is !i.
The linux assembler avra comes without the controller definition files. They need to copied from the Atmel AVR
Studio. Please use the version 1 of the files from the AvrAssembler/appnotes directory. The Makefiles in
the applications expect the files in the directory ~/lib/avra. Please note that these files are horribly outdated
and do not cover all controller types. For those controllers you need the Atmel AVR Assembler version2. See next
note.
First you need a working setup of a recent wine. Then put the avrasm2.exe and the Appnotes directory
somewhere on your system. Then edit the makefile to look similiar too:
AVRDUDE=/usr/local/bin/avrdude
AVRASM=wine ~/projects/avr/AvrAssembler2/avrasm2.exe
AVRASMOPTS=-fI -I ~/projects/avr/AvrAssembler2/Appnotes -e [email protected] -l [email protected] -m [email protected]
p8: p8.hex
$(AVRDUDE) $(PP) -p atmega644 -e -U flash:w:p8.hex:i -U eeprom:w:p8.hex.eep:i
please note that the file names are slightly different from the avra generated code. Good luck.
You can use any resource if you take care. There are some things you need to obey: Never use the T flag in the
machine status register SREG. Only the CPU registers named temp0..temp5 are save to use without the need of
restoration. Any other register change may be harmful.
18 Chapter 2. FAQ
CHAPTER
THREE
TECHNICAL GUIDE
The first steps require an ATmega micro controller with an RS232 connection to an PC or a terminal like the
VT100 or similar hardware. A customization may change these requirements.
amforth has a simple user interface. Connect your system to a serial terminal (or a PC) and you get, after pressing
the enter key, the forth prompt >
> cold
amforth 5.0 ATmega16 8000 kHz
> words
nr> n>r (i!) !i @i @e !e nip not s>d up! up@ ...
>
3.2 Hardware
3.2.1 Controller
amforth is designed to run on AVR Atmega micro controllers. It requires ca 8KB flash memory for the basic
system and can address 128KB of flash memory.
The ATtiny micro controllers and a a few ATmega types lack the minimum flash capacity. The ATtiny’s some
machine instructions as well.
Most bootloaders will not work with amforth since they do not provide an application programming interface to
rewrite a single flash cell. The default setup will thus replace any bootloader found with some core routines.
It is possible to change the word !i to use an API and work with existing bootloaders. !i is a deferred word that
can be re-targeted to more advanced words that may do address range checks, write success checks or simply turn
on/off LEDs to visualize the flash programming.
3.2.3 Fuses
Amforth uses the self programming feature of the ATmega micro controllers to work with the dictionary. It is ok
to use the factory default settings plus the changes for the oscillator settings. It is recommended to use a higher
CPU frequency to meet the timing requirements of the serial terminal.
19
AmForth Documentation, Release 5.3
Fuses are the main cause for problems with the flash write operations. If the !i operation fails, make sure that
the code for it is within the boot loader section. It is recommended to make the bootloader section as large as
the NRWW section, otherwise the basic machine instruction spm may fail silently and the controller becomes
unresponsive.
3.3.1 Overview
amforth is written using the standard Atmel AVR 8 bit assembly language. That does not mean that every word is
actually written in assembly language however. Most of the words are written in forth itself, but are precompiled
into the assembler syntax. This solves the chicken-and-egg problem: how to compile the compiler words.
The source code can be processed with both the AVR Studio and the Linux avr assembler avra.
amforth consists of a great number of small source files. Nearly all words are coded in their own source files. These
files are organized with include files, named after the pattern dict*.inc or are located in the core/dict
directory.
The include hierarchy is as follows: Top level is the application specific file (template.asm). It includes the file
core/amforth.asm only. This file includes two files from the core/dict directory: rww.inc and nrww.inc.
In addition two files from the application directory are included as well: dict_appl.inc for the low address
words (RWW space) and dict_appl_core.inc for the high address space words (NRWW).
Currently only one optional dict file may be added to the user supplied files: compiler2.inc. It contains words
which are useful but not strictly necessairy.
There are additional files: amforth.asm and macros.asm. The first one is the master file and the only one
the application needs to include. The file macros.asm contains some useful assembler macros that make the
source code easier to read. user.inc contains the layout of the system user area.
Every Atmega has its own specific settings. They are based on the official include files provided by Atmel and
define the important settings for the serial IO port (which port and which parameters), the interrupt vectors and
some macros.
Adapting another ATmega micro controller is as easy as copy and edit an existing file from a similar type.
The last definition is a string with the device name in clear text. This string is used within the word VER.
Every build of amforth is bound to an application. There are a few sample applications, which can be used either
directly (AVR Butterfly) or serve as a source for inspiration (template application).
The structure is basically always the same. First the file macros.asm has to be included. After that some
definitions need to done: The size of the Forth buffers, the CPU frequency, initial terminal settings etc. Then the
device specific part needs to be included and as the last step the amforth core is included.
For a comfortable development cycle the use of a build utility such as make or ant is recommended. The assembler
needs a few settings and the proper order of the include directories.
3.4 Architecture
3.4.1 Overview
amforth is a 16 bit Forth implementing the indirect threading model. The flash memory contains the whole
dictionary. A few EEPROM cells are used to hold initial values and the dictionary pointers. The RAM contains
buffers, variables and the stacks.
The compiler is a classic compiler without any optimization support.
amforth uses all of the CPU registers to hold vital data: The data stack pointer, the instruction pointer, the user
pointer, and the Top-Of-Stack cell. The hardware stack is used as the return stack. Some registers are used for
temporary data in primitives.
The Forth VM has a few registers that need to be mapped to the microcontroller registers. The mapping has been
extended over time and may cover all available registers. The actual coverage depends on the amount of additional
packages. The default settings are shown in the table Register Mapping.
Register Mapping
Threading Model
AmForth implements the classic indirect threaded variant of forth. The registers and their mappings are shown in
table Register Mapping.
3.4. Architecture 21
AmForth Documentation, Release 5.3
Inner Interpreter
For the indirect threading model an inner interpreter is needed. The inner interpreter does the interrupt handling
too. It repeatedly reads the cell, the IP points to, takes this number as the address for the next code segment and
jumps to that code. It is expected that this code segment does a jump back to the inner interpreter (NEXT). The
IP is incremented by 1 just before the jumps are done to get the next cell.
Check_Interrupt
W <- [IP] ; read at IP
IP <- IP+1 ; advance IP
X <- [W] ; EXECUTE phase, W points to execution token
JMP [X] ; read execution token and execute its code
NEXT
The NEXT routine is the core of the inner interpreter. It does the mapping between the execution tokens and the
corresponding machine code. It consists of 4 steps which are executed for every forth word.
The first step is to check whether an interrupt needs to be handled. It is done by looking at the T flag in the
machine status register. If it is set, the code jumps to the interrupt handling part.
The next step is to read the cell the IP points to and stores this value in the W register. For a COLON word W
contains the address of the code field.
The 3rd step is to increase the IP register by 1.
The 4th step is the EXECUTE step.
EXECUTE
This operation is the JUMP. It reads the content of the cell the W register points to. The result is stored in the
scratch pad register X. The data in X is the address of the machine code to be executed in the last step. This step is
used by the forth command EXECUTE too. The forth command does not get the address of the next destination
from the current IP but from the data stack.
This last step finally jumps to the machine code pointed to by the X scratch pad register.
DO COLON
DO COLON (aka NEST) is the subroutine call. It pushes the IP onto the return stack. It then increments W
by one flash cell, so that it points to the body of the (colon) word, and sets IP to that value. Then it continues
with NEXT, which begins executing the words in the body of the (parent) colon word. Note that W points to the
execution token of the current word, so W+1 points to the parameter field (body) of the forth word.
push IP
IP <- W+1
JMP NEXT
EXIT
The code for EXIT (aka UNNEST) is the return from a subroutine. It is defined in the forth word EXIT in the
dictionary. It reads the IP from the return stack and jumps to NEXT. The return stack pointer is incremented by 2
(1 flash cell).
pop IP
JMP NEXT
The interpreter is a line based command interpreter. It based upon REFILL to acquire the next line of characters,
located at a position SOURCE points to. While processing the line, the pointer >IN is adjusted accordingly. Both
words REFILL and SOURCE are USER based deferred words which allows to use any input source on a thread
specific level. The interpreter itself does not use any static buffers or variables (>IN is a USER variable as well).
A given string is handled by INTERPRET which splits it into whitespace delimited words. Every word is pro-
cessed using a list of recognizers. Processing ends either when the string end is reached or an exception occurs.
SOURCE provides an addr/len string pair that does not change during processing. The task of REFILL is to fill
the string buffer, SOURCE points to when finished.
There is one default input source: The terminal input buffer. This buffer gets filled with REFILL-TIB that reads
from the serial input buffers (KEY). SOURCE points to the Terminal Input Buffer itself. Another input source
are plain strings, used by EVALUATE.
Recognizer
Recognizer are a part of the text (command) interpreter. They are responsible for analyzing a single word. The
result consists of two elements: The actual data (if any) and an object like identifier connected with certain
3.4. Architecture 23
AmForth Documentation, Release 5.3
Interpret
End Do Recognizer
Check State
Compile Interpret
Compile Execute
Recognizer List
The interpreter uses a list of recognizers. They are managed with the words get-recognizer and
set-recognizer.
: place-rec ( xt -- )
get-recognizer 1+ set-recognizer
;
’ rec:foo place-rec
The entries in the list are called in order until the first one returns a different result but r:fail. If the list is
exhausted and no one succeeds, the r:fail is delivered nevertheless and leads to the error reactions.
The standard recognizer list is defined as follows
: default-recs
[’] rec:intnum [’] rec:find
2 set-recognizer
;
INTERPRET
The interpreter is responsible to split the source into words and to call the recognizers. It also maintains the state.
: interpret
begin
parse-name ?dup if drop exit then
do-recognizer ( addr len -- i*x r:table )
state @ if 1+ then \ get compile time action
@i execute ?stack
again
;
do-recognizer always returns a valid method table. If no recognizer succeeds, the r:fail is returned with
the addr/len of the unknown-to-handle word.
API
Every recognizer has a method table for methods to handle the data inside the forth interpreter and a word to parse
a word.
\ order is important!
:noname ... ; \ interpret action
:noname ... ; \ compile action
:noname ... ; \ postpone action
recognizer: r:foo
The word rec:foo is the actual recognizer. It analyzes the string it gets. There are two results possible: Either
the word is recognized and the address of the method table is returned or a failure information is generated which
is actually a predefined method table named r:fail.
The calling parameters to rec:foo are the address and the length of a word in RAM. The recognizer must not
change it. The result (i*x) is the parsed and converted data and the method table to deal with it.
There is a standard method table that does not require additional data (i*x is empty) and which is used to commu-
nicate the “not-recognized” information: r:fail. Its method table entries throw the exception -13 if called.
Other pre-defined method tables are r:intnum to deal with single cell numeric data, r:intdnum to work with
double cell numerics and r:find to execute, compile and postpone execution tokens from the dictionary.
3.4. Architecture 25
AmForth Documentation, Release 5.3
The words in the method tables get the output of the recognizer as input on the data stack. They are excpected to
consume them during their work.
Default (Fail)
This is a special system level recognizer. It is never called, its method table (r:fail) is used as both a error flag and
for the final error actions. Its methods get the addr/len of a single word. They consume it by printing the string
and throwing an exception when called. The effect is to get back to the command prompt if catched inside the
quit loop.
:noname type -13 throw ; dup dup
recognizer: r:fail
NUMBER
The number recognizer identifies numeric data in both single and double precision. Depending on the actual data
width, two different methods tables are returned.
The postpone action follows the standard definitions with not allowing to postpone numbers. Instead the number
is printed and an exception is thrown.
’ noop
’ literal
:noname . -48 throw ;
recognizer: r:intnum
’ noop
’ 2literal
:noname d. -48 throw ;
recognizer: r:intdnum
FIND
This recognizer tries to find the word in the dictionary. If sucessful, the execution token and the flags are re-
turned. The method table contains words to execute and correctly deal with immediate words for compiling and
postponing.
( XT flags -- )
:noname drop execute ;
:noname 0> if compile, else execute then ;
:noname 0> if postpone [compile] then , ;
recognizer: r:find
then
r:fail
;
3.4.5 Stacks
Data Stack
The data stack uses the CPU register pair YH:YL as its data pointer. The Top-Of-Stack element (TOS) is in a
register pair. Compared to a straight forward implementation this approach saves code space and gives higher
execution speed (approx 10-20%). Saving even more stack elements does not really provide a greater benefit
(much more code and only little speed enhancements).
The data stack starts at a configurable distance below the return stack (RAMEND) and grows downward.
Return Stack
The Return Stack is the hardware stack of the controller. It is managed with push/pop assembler instructions. The
default return stack starts at RAMEND and grows downward.
3.4.6 Interrupts
Amforth routes the low level interrupts into the forth inner interpreter. The inner interpreter switches the execution
to a predefined word if an interrupt occurs. When that word finishes execution, the interrupted word is contin-
ued. The interrupt handlers are completely normal forth colon words without any stack effect. They do not get
interrupted themselves.
The processing of interrupts takes place in two steps: The first one is the low level part. It is called whenever an
interrupt occurs. The code is the same for all interrupts. It takes the number of the interrupt from its vector address
and stores this in a RAM cell. Then the low level ISR sets the T flag in the status register of the controller and
returns with RET.
The second step does the inner interpreter. It checks the T-flag every time it is entered and, if it is set, it switches
to interrupt handling at forth level. This approach has a penalty of 1 CPU cycle for checking and skipping the
branch instruction to the isr forth code if no interrupt occurred.
If an interrupt is detected, the forth VM clears the T-flag and continues with the word ISR-EXEC. This word
reads the currently active interrupt number and calls the associated execution token. When this word is finished,
the word ISR-END is called. This word clears the interrupt flag for the controller (RETI).
This interrupt processing has two advantages: There are no lost interrupts (the controller itself disables interrupts
within interrupts and re-transmits newly discovered interrupts afterwards) and it is possible to use standard forth
words to deal with any kind of interrupts.
Interrupts from some hardware sources (e.g. the usart) need to be cleared from the Interrupt Service Routine. If
this is not done within the ISR, the interrupt is re-triggered immediately after the ISR returned control.
The downside is a relatively long latency since the the forth VM has to be synchronized with the interrupt handling
code in order to use normal colon words as ISR. This penalty is usually small since only words in assembly can
cause the delay.
3.4. Architecture 27
AmForth Documentation, Release 5.3
COLD
Execute Word
T Flag Set?
Yes No
Next XT is ISR_EXEC
See also:
Interrupt Service Routines Interrupt Critical Section
3.4.7 Multitasking
amforth does not implement multitasking directly. It provides the basic functionality however. Within IO words
the deferred word PAUSE is called whenever possible. This word is initialized to do nothing (NOOP).
3.4.8 Exceptions
Amforth uses and supports exceptions as specified in the ANS wordset. It provides the CATCH and THROW
commands. The outermost catch frame is located at the interpreter level in the word QUIT. If an exception with
a negative value is catched, QUIT will print a message with this number and and re-start itself. Positive values
silently restart QUIT.
The next table lists the exceptions, amforth uses itself.
The User Area is a special RAM storage area. It contains the USER variables and the User deferred definitions.
Access is based upon the value of the user pointer UP. It can be changed with the word UP! and read with UP@ .
The UP itself is stored in a register pair.
The size of the user area is determined by the size the system itself uses plus a configurable number at compile
time. For self defined tasks this user supplied number can be changed for task local variables.
The first USER area is located at the first data address (usually RAMSTART).
Address offset (bytes) Purpose
0 Multitasker Status
2 Multitasker Follower
4 RP0
6 SP0
8 SP (used by multitasker)
10 HANDLER (exception handling)
12 BASE (number conversion)
14 EMIT (deferred)
16 EMIT? (deferred)
18 KEY (deferred)
20 KEY? (deferred)
22 SOURCE (deferred)
24 >IN
26 REFILL (deferred)
The User Area is used to provide task local information. Without an active multitasker it contains the starting
values for the stackpointers, the deferred words for terminal IO, the BASE variable and the exception handler.
The multitasker uses the first 2 cells to store the status and the link to the next entry in the task list. In that situation
the user area is/can be seen as the task control block.
Beginning with release 3.7 the USER area has been split into two parts. The first one called system user area
contains all the variables described above. The second one is the application user area that contains all variables
defined with the USER command. The default application user area is empty and by default of size zero.
Word lists and environment queries are implemented using the same structure. The word list identifier is a EEP-
ROM address that holds the name field address of the first word in the word list.
Environment queries are normal colon words. They are called within environment? and leave there results at the
data stack.
find-name (und find for counted strings) uses an array of word list identifiers to search for the word. This list can
be accessed with get-order as well.
Wordlist Header
Wordlists are implemented as a single linked list. The list entry consists of 4 elements:
3.4. Architecture 29
AmForth Documentation, Release 5.3
3.4.11 Memories
Flash
The flash memory is divided into 4 sections. The first section, starting at address 0, contains the interrupt vector
table for the low level interrupt handling and a character string with the name of the controller in plain text.
The 2nd section contains the low level interrupt handling routines. The interrupt handler is very closely tied to the
inner interpreter. It is located near the first section to use the faster relative jump instructions.
The 3rd section is the first part of the dictionary. Nearly all colon words are located here. New words are appended
to this section. This section is filled with FFFF cells when flashing the controller initially. The current write pointer
is the DP pointer.
The last section is identical to the boot loader section of the ATmegas. It is also known as the NRWW area. Here
is the heart of amforth: The inner interpreter and most of the words coded in assembly language.
The reason for this split is a technical one: to work with a dictionary in flash the controller needs to write to the
flash. The ATmega architecture provides a mechanism called self-programming by using a special instruction and
a rather complex algorithm. This instruction only works in the boot loader/NRWW section. amforth uses this
instruction in the word I!. Due to the fact that the self programming is a lot more then only a simple instruction,
amforth needs most of the forth core system to achieve it. A side effect is that amforth cannot co-exist with classic
boot loaders. If a particular boot loader provides an API to enable applications to call the flash write operation,
amforth can be restructured to use it. Currently only very few and seldom used boot loaders exist that enable this
feature.
Atmegas can have more than 64 KB Flash. This requires more than a 16 bit address, which is more than the cell
size. For one type of those bigger atmegas there will be an solution with 16 bit cell size: Atmega128 Controllers.
They can use the whole address range with an interpretation trick: The flash addresses are in fact not byte addresses
but word addresses. Since amforth does not deal with bytes but cells it is possible to use the whole address range
with a 16 bit cell. The Atmegas with 128 KBytes Flash operate slightly slower since the address interpretation
needs more code to access the flash (both read and write). The source code uses assembly macros to hide the
differences.
An alternative approach to place the elements in the flash shows picture . Here all code goes into the RWW section.
This layout definitely needs a routine in the NRWW section that provides a cell level flash write functionality. The
usual boot loaders do not have such an runtime accessible API, only the DFU boot loader from atmel found on
some USB enabled controllers does.
0x00
Interrupt Vectors
Startup code
Dictionary
dict_appl.inc
(pre-compiled colons)
DP
Free Flash
amforth_start (NRWW_ST
Inner Interpreter
Dictionary
dict_appl_core.inc
(primitives)
FLASH_END
0x00
Interrupt Vectors
Startup code
Inner Interpreter
Dictionary
dict_appl.inc
Dictionary
dict_appl_core.inc DP
Free Flash
0x1FFFF
3.4. Architecture 31
AmForth Documentation, Release 5.3
The unused flash area beyond 0x1FFFF is not directly accessible for amforth. It could be used as a block device.
Flash Write
The word performing the actual flash write operation is I! (i-store). This word takes the value and the address of
a single cell to be written to flash from the data stack. The address is a word address, not a byte address!
The flash write strategy follows Atmel’s appnotes. The first step is turning off all interrupts. Then the affected
flash page is read into the flash page buffer. While doing the copying a check is performed whether a flash erase
cycle is needed. The flash erase can be avoided if no bit is turned from 0 to 1. Only if a bit is switched from 0 to 1
must a flash page erase operation be done. In the fourth step the new flash data is written and the flash is set back
to normal operation and the interrupt flag is restored. The whole process takes a few milliseconds.
This write strategy ensures that the flash has minimal flash erase cycles while extending the dictionary. In addition
it keeps the forth system simple since it does not need to deal with page sizes or RAM based buffers for dictionary
operations.
3.4.12 EEPROM
The built-in EEPROM contains vital dictionary pointer and other persistent data. They need only a few EEPROM
cells. The remaining space is available for user programs. The easiest way to use the EEPROM is a VALUE. There
intended design pattern (read often, write seldom) is like that for the typical EEPROM usage. More information
about values can be found in the recipe Values.
Another use for EEPROM cells is to hold execution tokens. The default system uses this for the turnkey vector.
This is an EEPROM variable that reads and executes the XT at runtime. It is based on the DEFER/IS standard. To
define a deferred word in the EEPROM use the Edefer definition word. The standard word IS is used to put a new
XT into it.
Low level space management is done through the the EHERE variable. This is not a forth value but a EEPROM
based variable. To read the current value an @e operation must be used, changes are written back with !e. It
contains the highest EEPROM address currently allocated. The name is based on the DP variable, which points to
the highest dictionary address.
3.4.13 RAM
The RAM address space is divided into three sections: the first 32 addresses are the CPU registers. Above come
the IO registers and extended IO registers and finally the RAM itself.
amforth needs very little RAM space for its internal data structures. The biggest part are the buffers for the
terminal IO. In general RAM is managed with the words VARIABLE and ALLOT.
Forth defines a few transient buffer regions for various purposes. The most important is PAD, the scratch buffer. It
is located 100 bytes above the current HERE and goes to upper addresses. The Pictured Numeric Output is just at
PAD and grows downward. The word WORD uses the area above HERE as it’s buffer to store the just recognized
word from SOURCE.
Ram Structure shows an RAM layout that can be used on systems without external RAM. All elements are located
within the internal memory pool.
Another layout, that makes the external RAM easily available is shown in Alternative RAM Structure. Here are
the stacks at the beginning of the internal RAM and the data space region. All other buffers grow directly into the
external data space. From an application point of view there is not difference but a speed penalty when working
with external RAM instead of internal.
With amforth all three sections can be accessed using their RAM addresses. That makes it quite easy to work with
words like C@. The word ! implements a LSB byte order: The lower part of the cell is stored at the lower address.
0..0x1f Register
0x20..0x5f IO Register
0x60 .. RAMSTART
IO Register
ISR Vectors
allocated memory
HLD
PAD
Data Stack
Return Stack
XRAM follows
0..0x1f Register
0x20..0x5f IO Register
0x60 .. RAMSTART
IO Register
RAM_START
Data Stack
Return Stack
Allocated Memory
HLD
PAD
3.4. Architecture 33
AmForth Documentation, Release 5.3
For the RAM there is the word Rdefer which defines a deferred word, placed in RAM. As a special case there is
the word Udefer , which sets up a deferred word in the user area. To put an XT into them the word IS is used.
This word is smart enough to distinguish between the various Xdefer definitions.
3.4.14 DOES>
DOES> is used to change the runtime action of a word that create has already defined.
Its working is described best using a simple example: defining a constant. The standard word constant does
exactly the same.
> : con create , does> @i ;
ok
> 42 con answer
ok
> answer .
42 ok
The first command creates a new command con. With it a new word gets defined, in this example answer. con
calls create, that parses the source buffer and creates a wordlist entry answer. After that, within con the top-of-
stack element (42) is compiled into the newly defined word. The does> changes the runtime of the newly defined
word answer to the code that follows does>.
does> is an immediate word. That means, it is not compiled into the new word (con) but executed. This compile
time action creates a small data structure similar to the wordlist entry for a noname: word. The address of this data
structure is an execution token. This execution token replaces the standard XT that create has already written for
words that are defined using con. This leads inevitably to a flash erase cycle.
3.5 Implementation
The ANS 94 standard defines three major data regions: name space, code space and data space. The Atmega
system architecture uses three memory types too: Flash, RAM and EEPROM. These three memory types have
their own address space independently from the others. Amforth does not unify these address spaces into one.
Amforth uses the flash memory as the location for all standard data spaces: name, code and data space. Contrary
to the standard some words that should operate on the data space use RAM adresses instead. These words are
HERE, @ (fetch), ! (store) and simimliar. Similiarly the so called transient regions are in RAM as well.
Other words like , (comma) operate on the flash address and thus directly in the dictionary.
The dictionary can be seen from several points of view. One is the split into two memory regions: NRWW and
RWW flash. This is the hardware view. NRWW flash cannot be read during a flash write operation, NRWW means
Non-Read-While-Write. This makes it impossible to change there anything at runtime. On the other hand is this
the place, where code resides that can change the RWW (Read-While-Write) part of the flash. For AmForth, the
command !i does this work: It changes a single flash cell in the RWW section of the flash. This command hides
all actions that are necessary to achieve this.
The NRWW section is usually large enough to hold the interpreter core and most (if not all) words coded in
assembly (not to be confused with the words that are hand-assembled into a execution token list) too. Having all
of them within a rather small memory region makes it possible to use the short-ranged and fast relative jumps
instead of slower full-range jumps necessary for RWW entries.
Another point of view to the dictionary is the memory allocation. The key for it is the dictionary pointer dp. It
is a EEPROM based VALUE that stores the address of the first unused flash cell. With this pointer it is easy to
allocate or free flash space at the end of the allocated area. It is not possible to maintain “holes” in the address
range. To append a single number to the dictionary, the command , is used. It writes the data and increases the DP
pointer accordingly:
\ ( n -- )
: , dp !i dp 1+ to dp ;
To free a flash region, the DP pointer can be set to any value, but a lot of care has to be taken, that all other system
data is still consistent with it.
The next view point to the dictionary are the wordlists. A wordlist is a single linked, searchable list of entries.
All wordlists create the forth dictionary. A wordlist is identified by its wid, an EEPROM address, that contains
the address of the first entry. The entries themselves contain a pointer to the next entry or ZERO to indicate
End-Of-List. When a new entry is added to a list it will be the first one of this wordlist afterwards.
A new wordlist is easily created: Simply reserve an EEPROM cell and initialize its content with 0:
: wordlist ( -- wid )
ehere 0 over !e
dup cell+ to ehere ;
This wid is used to create new entries. The basic procedure to do it is create:
: create
(create) reveal
postpone (constant) ;
(create) parses the current source to get a space delimited string. The next step is to determine, into which
wordlists the new entry will be placed and finally, the new entry is created, but it is still invisible:
: (create)
parse-name
wlscope
dup >r
header
r> smudge 2! ;
The header command starts a new dictionary entry. The first action is to copy the string from RAM to the flash.
The second task is to create the link for the wordlist management
: header
dp >r
\ copy the string from RAM to flash
r> @e ,
\ minor housekeeping
;
smudge is the address of a 4 byte RAM location, that buffers the access information. Why not not all words are
immediately visible is something, that the forth standard requires. The command reveal un-hides the new entry
by adjusting the content of the wordlist identifier to the address of the new entry:
: reveal
smudge @ ?dup if \ check if valid data
smudge 2+ @ !e \ update the wid
0 smudge ! \ invalidate
then ;
The command wlscope can be used to change the wordlist that gets the new entry. It is a deferred word that
defaults to get-current.
The last command postpone (constant) writes the runtime action, the execution token (XT) into the newly cre-
ated word. The XT is the address of executable machine code that the forth inner interpreter calls (see Inner
Interpreter). The machine code for (constant) puts the address of the flash cell that follows the XT on the data
stack.
3.5. Implementation 35
AmForth Documentation, Release 5.3
3.5.3 Compiler
The Amforth Compiler is based upon immediate words. They are always executed, regardless of the value in the
state variable. All non-immediate words get compiled verbatim with their respective execution token. It is
simply appended to the current DP location.
Immediate words are usually executed (unless some special action such as postpone is applied). The immediate
words do usually generate some data or compile it to the dictionary. They are not compiled with their execution
token.
There are no optimization steps involved. The XT are written immediately into the dictionary (flash).
The inner interpreter, the forth virtual machine, can, just like a real CPU, only execute words, one after the next.
This linear control flow is usually not sufficient to do real work. The Forth VM needs to be redirected to other
places instead of the next one, often depending on runtime decisions.
Since Edsgar Dijkstra the structured programming is the preferred way to do it. AmForth provides all kinds of
them: sequences, selections and repetitions. Sequences are the simple, linear execution of consecutive words.
Selections provide a conditional jump over code segments. They are usually implemented with the ìf command.
Multiple selections can be made with case. Repetitions can be unlimited or limited. Limited Repetitions can use
flags and counter/limits to leave the loop.
There is also support for out-of-band control flow: Exceptions. They provide some kind of emergency exits to
solve hard problems. They can be catched at any level up to the outer text interpreter. It will print a message on
the command terminal and will wait for commands.
Building Blocks
All control structures can be implemented using jumps and conditional jumps. Every control operation results in
either a forward or a backward jump. Thus 6 building blocks are needed to create them all: (branch), (0branch),
>mark, <mark, >resolve and <resolve. None of them are directly accessible however. Most of these words are
used in pairs. The data stack is used as the control flow stack. At runtime the top-of-stack element is the flag. All
words are used in immediate words. They are executed at compile time and produce code for the runtime action.
(branch) is a unconditional jump. It reads the flash cell after the command and takes it as the jump destination.
Jumps can be at any distance in any direction. (0branch) reads the Top-Of-Stack element and jumps if it is zero
(e.g. logically FALSE). If it is non-zero, the jump is not made and execution continues with the next XT in the
dictionary. In this case, the branch destination field is ignored. These two words are implemented in assembly. A
equivalent forth implementation would be
: (branch) r> 1+ @i >r ;
: (0branch) if (branch) else r> 1+ >r then ;
: <mark dp ;
: <resolve ?stack , ;
The place holder -1 in >mark prevents a flash erase cycle when the jump is resolved using the !i in >resolve.
The ?stack checks for the existence of a data stack entry, not for a plausible value. It the data stack is empty, an
exception -4 is thrown.
: ?stack depth 0< if -4 throw then ;
Highlevel Structures
The building blocks described above create the standard control structures: conditional execution and various loop
constructs.
Conditional Execution
The conditional execution compiles a forward jump to another location. The jump destination is resolved with
then. An else terminates the first jump and starts a new one for the final then. This way an alternate code block is
executed at runtime depending on the flag given to the if.
: if postpone (0branch) >mark ; immediate
: else postpone (branch) >mark swap >resolve ; immediate
: then >resolve ; immediate
There is a rarely used variant of the if command, that compiles an unconditional forward branch: ahead. It
needs to be paired with a then to resolve the branch destination too. An else would not make any sense, but is
syntactically ok.
: ahead postpone (branch) >mark ; immediate
There are more variants of multiple selections possible. The case structure is based upon nested if‘s. Computed
goto’s can be implemented with jump tables whith execution tokens as code blocks. Examples are in the lib
directory.
Conditional Loops
The loop commands create a structure for repeated execution of code blocks. A loop starts with a begin to which
the program flow can jump back any time.
: begin <mark ; immediate
The first group of loop command are created with again and until. They basically differ from each with the branch
command they compile:
: until postpone (0branch) <resolve ; immediate
: again postpone (branch) <resolve ; immediate
The other loop construct starts with begin too. The control flow is further organized with while and repeat. while
checks wether a flag is true and leaves the loop while repeat unconditionally repeats it.
: while postpone (0branch) >mark swap ; immediate
: repeat again >resolve ; immediate
Counted Loops
Counted loops need to store the starting address and the address of the last word of the loop body. The first
one is needed to jump back if the counter has not yet reached its limit. The forward jump is made in leave to
unconditionally exit the loop body.
: do postpone (do) >mark <mark ; immediate
: loop postpone (loop) <resolve >resolve ; immediate
3.5. Implementation 37
AmForth Documentation, Release 5.3
The other loop commands ?do and +loop are almost identical to their respective counterparts, the compile only a
different runtime action to their goals.
The runtime action of do (the (do)) puts three information onto the return stack: The loop counter, the loop limit
and the destination address for the leave. The first two parameters are taken from the data stack at runtime, the
leave-address comes from the compiler (from the >mark).
The runtime of loop (the (loop)) checks the limits and with 0branch decides whether to repeat the loop body with
the next loop counter value or to exit the loop body. If the loop has terminated, it cleans up the return stack. The
+loop works almost identically, except that it reads the loop counter increment from the data stack.
The access to the loop counters within the loops is done with i and j. Since the return stack is used to manage the
loop runtime, it is necessary to clean it up. This is done with either unloop or leave. Note that unloop does not
leave the loop!
amforth implements most or all words from the ANS word sets CORE, CORE EXT, EXCEPTION and DOUBLE
NUMBERS. A loadable floating point library that contains the basic routines is available. Words from the word
sets LOCALS and FILE-ACCESS are dropped completely. The others are partially implemented.
Al words from the CORE word set are available. CORE EXT drops the words C”, CONVERT, EXPECT, SPAN,
and ROLL.
Loop counters are checked on signed compares.
Block
amforth has limited block support with I2C/TWI serial eeprom chips with 2 byte addresses.
Double Number
Double cell numbers work as expected. Not all words are implemented. Entering them directly using the dot-
notation work for dots at the end of the number, not if the dot is somewhere within it.
Exception
Exceptions are fully supported. The words ABORT and ABORT” use them internally.
The THROW codes -1, -2 and -13 work as specified.
The implementation is based upon a variable HANDLER which holds the current return stack pointer position.
This variable is a USER variable.
Facility
The basic system uses the KEY? and EMIT? words as deferred words in the USER area.
The word MS is implemented with the word 1MS which busy waits almost exactly 1 millisecond. The calculation
is based upon the frequency specified at compile time.
The words TIME&DATE, EKEY, EKEY>CHAR are not implemented.
To control a VT100 terminal the words AT-XY and PAGE are written in forth code. They emit the ANSI control
codes according to the VT100 terminal codes.
File Access
amforth does not have filesystem support. It does not contain any words from this word set.
Floating Point
amforth has a loadable floating point library. It contains the basic words to deal with single precision floats.
The floats are managed on the standard data stack. After loading the library floats can be entered directly at the
command prompt. Some speed sensitive words are available as assembly code as well.
Locals
Memory Allocation
amforth does not support the words from the memory allocation word set.
Programming Tools
Variants of the words .S, ? and DUMP are implemented or can easily be done. The word SEE is available as well.
STATE works as specified.
The word WORDS does not sort the word list and does not take care of screen sizes.
The words ;CODE and ASSEMBLER are not supported. amforth has a loadable assembler which can be used
with the words CODE and END-CODE .
The control stack commands CS-ROLL , and , CS-PICK are not implemented. The compiler words operate with
the more traditional MARK / RESOLVE word pairs.
FORGET is not implemented since it would be nearly impossible to reset the search order word list with reason-
able efforts. The better way is using MARKER from the library.
An EDITOR is not implemented.
[IF], [ELSE] and [THEN] are not implemented.
Amforth supports the ANS Search Order word list. A word list consist of a linked list of words in the dictionary.
There are no limits on the number of word lists defined. Only the length of the active search order is limited:
There can be up to 8 entries at any given moment. This limit can be changed at compile time in the application
definition file.
Internally the word list identifier is the address where the word list start address is stored in the EEPROM. Creating
a new word list means to allocate a new EEPROM cell. Since the ANS standard does not give named word list
there is library code available that uses the old fashioned vocabulary.
Strings
amforth provides the defer/is, buffer: and the structure extensions from the forth 200x standards.
Defer and IS
defer give the possibility of vectored execution. Amforth has 3 different kind of such vectors, varying in how they
are stored: EEPROM, RAM or the USER area. The EEPROM makes it possible to save the settings permanently,
the RAM enables frequent changes. Finally the user area is for multitasking.
Buffer:
The buffer allocates a named memory (RAM) region. It is superior to the usual create foo xx allot since amforth
has a non-unified memory model and the code snippet does not the same as an unified memory model forth (with
the dictionary being at the same memory as the allot command works).
Structures
3.6.3 Amforth
COLD
The startup code is in the file cold.asm. It gets called directly from the address 0 vector.
This assembly part of the startup code creates the basic runtime environment to start the virtual forth machine. It
sets up the stack pointers and the user pointer and places the forth instruction pointer on the word WARM. Then
it boots the forth virtual machine by jumping to the inner interpreter.
The start addresses of the stacks are placed to the user area for later use as well.
WARM
The word WARM is the high level part of the forth VM initialization. When called from within forth it is the
equivalent to a RESET. WARM initializes the PAUSE deferred word to do nothing, calls the application defined
TURNKEY action and finally hands over to QUIT.
TURNKEY
The turnkey is a EEPROM deferred word that points to an application specific startup word.
Its main task is to initialize the character IO to enable the forth interpreter to interact with the command prompt.
The examples shipped with amforth do this by “opening” the serial port, switching to decimal number conversion
and setting up the character IO deferred words (KEY, EMIT etc).
QUIT
QUIT initializes both data and return stack pointers by reading them from the user area and enters the traditional
ACCEPT – INTERPRET loop that never ends. It provides the topmost exception catcher as well. Depending on
the exception thrown, it prints an error message and restarts itself.
MCU Access
amforth provides wrapper words for the micro controller instructions SLEEP and WDR (watch dog reset). To
work properly, the MCU needs more configuration. amforth itself does not call these words.
Assembler
Lubos Pekny has written an assembler for amforth. To support it, amforth provides the two words CODE and
END-CODE. The first creates a dictionary entry and sets the code field to the data filed address. The interpreter
will thus jump directly into the data field assuming some machine code there. The word END-CODE places
a JUMP NEXT into the data field. This finishes the machine instruction execution and jumps back to the forth
interpreter.
Memories
Atmega micro controller have three different types of memory. RAM, EEPROM and Flash. The words @ and !
work on the RAM address space (which includes IO Ports and the CPU register), the words @e and !e operate
on the EEPROM and @i and !i deal with the flash memory. All these words transfer one cell (2 bytes) between
the memory and the data stack. The address is always the native address of the target storage: byte-based for
EEPROM and RAM, word-based for flash. Therefore the flash addresses 64 KWords or 128 KBytes address
space.
External RAM shares the normal RAM address space after initialization (which can be done in the turnkey action).
It is accessible without further changes.
For RAM only there is the special word pair c@/c! which operate with the lower half of a stack cell. The upper
byte is either ignored or set to 0 (zero).
All other types of external memory need special handling, which may be masked with the block word set.
Input Output
amforth uses character terminal IO. A serial console is used. All IO is based upon the standard words
EMIT/EMIT? and KEY/KEY?. Additionally the word /KEY is used to signal the sender to stop. All these
words are deferred words in the USER area and can be changed with the IS command.
The predefined words use an interrupt driven IO with a buffer for input and output. They do not implement a
handshake procedure (XON/XOFF or CTS/RTS). The default terminal device is selected at compile time.
These basic words include a call to the PAUSE command to enable the use of multitasking.
Other IO depend on the hardware connected to the micro controller. Code exists to use LCD and TV devices.
CAN, USB or I2C are possible as well. Another use of the redirect feature is the following: consider some input
data in external EEPROM (or SD-Cards). To read it, the words KEY and KEY? can be redirected to fetch the
data from them.
Strings
Strings can be stored in two areas: RAM and FLASH. It is not possible to distinguish between the storage areas
based on the addresses found on the data stack, it’s up to the developer to keep track.
Strings are stored as counted strings with a 16 bit counter value (1 flash cell) Strings in flash are compressed:
two consecutive characters (bytes) are placed into one flash cell. The standard word S” copies the string from the
RAM into flash using the word S, .
3.7 Tools
3.7.1 Host
There a few number of tools on the host side (PC) that are specifically written to support amforth. They are written
in script languages like Perl and python and should work on all major operating systems. They are not needed to
use amforth but may be useful.
3.7. Tools 41
AmForth Documentation, Release 5.3
The pd2amforth.pl script reads a part description file in XML format (comes with the Atmel Studio package) and
produces the controller specific devices/controllername/* files.
Documentation
The tool makerefcard reads the assembly files from the words subdirectory and creates a reference card. The
resulting LaTeX file needs to be processed with latex to generate a nice looking overview of all words available
in the amforth core system.
The command make-htmlwords creates the linked overview of all words on the amforth homepage.
Uploader
To transfer forth code to the micro controller some precautions need to taken. During a flash write operation all
interrupts are turned off. This may lead to lost characters on the serial line. One solution is to send very slowly
and hope that the receiver gets all characters. A better solution is to send a character and wait for the echo from
the controller. This may sound awfully slow at the glance but it turned out to be a fast and reliable strategy.
An example for the first strategy can be used with the program ascii-xfer. Calling it with the command line
parameters
$ ascii-xfr -s -c $delayChar -l $delayLine file > $tty
will work but the upload of longer files needs a very long time: $delayChar can be 1 or 2 ms, $delayLine around
800 ms.
Uploader++
The powerful Python script amforth-shell.py is using echo to regulate uploading. It recognizes Forth comments,
single and multi line, and skips uploading them. The shell also features automatic file inclusion via #include
filename.frt meta commands and, what can save a lot of dictionary space and clutter, it does constant substitution
for the AVR register names and the project’s own definitions (via a locally provided appl_defs.frt file). The
shell has much more to offer, please read its script.
3.7.2 Controller
There are a few tools that may be useful on the controller. They are implemented as loadable forth code that may
affect internal data and work flows in a non-portable way. In particular are available a profiler (counting calls to
words), a call tracer (printing a stack trace while executing the words), a timing utility (benchme), a few memory
dump tools and a see that may be useful to revert the compilation process (gets some forth code from compiled
words).
See also:
Profiler Debug Shell Watcher Tracer
FOUR
COOKBOOK
The Cookbook is a collection of small and not so small recipes. Every recipe is intended to deal with exactly one
task. It is a living document, so expect changes at any time.
The example demonstrates a blinking LED. Most arduino’s have one attached to the port Digital-13. For this
recipe, the amforth system is already loaded onto the ardiuno. Instructions to do it are in the User’s Manual.
To quickly test the hardware start a terminal (e.g. screen /dev/ttyACM0 38400) and enter the following commands:
> $80 $24 c!
> $80 $25 c!
> $00 $25 c!
The LED turned on until the last command is executed. The character > is the command prompt, if you see it, you
can enter any commands. You’ll never enter that character yourselves. A command line can be up to 80 characters
long.
The commands above are pretty obscure. To make them easier to understand we define labels for some numbers,
so called constants:
> $25 constant PORTB
> $24 constant DDRB
The arduino uses its own numbering schema for pins, but for now we use the atmega one: digial-13 is the same as
bit 7 of port B. Port B has 8 pins and three registers to configure them. we need two of them: The Data Direction
Register (DDR) and the PORT (Output) Register. The third register is used for reading from the port (PIN).
The above commands can now be written as
> $80 DDRB c!
> $80 PORTB c!
> $00 PORTB c!
43
AmForth Documentation, Release 5.3
With this command line the interpreter learns a new command: led-init. This command can be called immediately.
> led-init
ok
>
It writes the number 128 (hex 80) to the register DDRB (hex 24) as defined above. This makes the 7th bit of
PORTB an Output pin.
Calling our newly defined word does not change anything visible. But with the next word, the LED will turn on:
: led-on $80 PORTB c! ;
Here the 7th bit will be set to 1, and that makes the led to be connected to VCC (5V) and it will turn on (the LED
is connected to ground already).
If the led-on command does not turn on the LED just call the led-init command (again). The led-init is needed
after an reset or power cycle as well.
Now that the led is active, we want a command to turn it off. One solution is to repeat the command from above:
0 PORTB c!. Smarter is a new command word:
: led-off 0 PORTB c! ;
You can now use the newly defined commands to turn the led on and off:
> led-on led-off led-on led-off
ok
>
Since there is no timing yet, you may not even see the led flash, amforth is pretty fast.
Our next word will simplify this and gives the real blink experience:
: led-blink led-on 500 ms led-off 500 ms ;
Calling this command will turn on the led, waits for half a second, turn it off again and waits another half a second
before returning to the command prompt.
With this command you can blink the led a few times
> led-blink led-blink led-blink
ok
>
The led will blink for a 3 seconds before the ok and returning to the command prompt.
To make it blink “forever”, we define another command word:
: blink-forever
." press any key to stop "
begin
led-blink
key?
until
key drop
;
Since this is our first command which needs more than 1 line, the interpreter acts more complex. It changes the
command prompt until the end of the command definition is reached (the command ;) The ouput in the terminal
window looks like
44 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
> : blink-forever
ok." press any key to stop"
okbegin
ok led-blink
ok key?
okuntil
okkey drop
ok;
ok
>
This word first prints some text (“press any key to stop”) and starts a loop. This loop lets the led blink once and
checks for a keystroke. If no key is pressed, the loops is repeated. If a key is pressed, the loop is terminated. The
last two commands are housekeeping: get the key pressed and forget it. Otherwise the key pressed would be the
first character of the next command line.
The advantage of defining many words is that you can test them immediately. Thus any further code can rely on
words already being tested. That makes debugging a lot easier. The drawback of that many words? You need to
remember their names.
Where to go next
This example is very basic. Next steps may involve library code like Digital Ports. Related to it are the Use of the
amforth-shell.py utility for files with forth code.
More Arduino related stuff is in Arduino Analog.
Accessing the Analog ports for reading needs the files lib/bitnames.frt for basic rou-
tines, the file appl/arduino/blocks/ports-arduinotype.frt for the actual ports and
appl/arduino/blocks/wiring_analog.frt for the code to do the work. After loading the
files, the Analog Conversion Module has to be initialized with the adc.init. This has to be done after a reset and
power cycle as well.
Now it is time to connect some hardware to one of the ports labled Analog In. Once this is done, some simple
commands will work:
> analog.1 adc.get u.
67 ok
>
The ADC on the ATmega has a resolution of 10 bits, thus a number between 0 and 1023 can be expected as the
result.
Note that the ADC module needs some time between two conversion. If you do it too fast, expect malfunctions or
even crashes. A simple 50 ms circumvent most problems.
\ continuously read the adc port
\ and print the new value if it
\ has changed considerably since last round
\ note the 50ms delay to keep things
\ run smoothly. A key press will
\ return to the command prompt
: analog-test
0
begin
( -- old )
analog.1 adc.get ( -- old new )
swap over ( -- new old new )
- abs 6 > ( -- new f )
50 ms ( wait...)
if dup u. then ( -- new )
key? ( -- new f )
until
key drop
drop ;
The Butterfly Demo board from Atmel uses an Atmega169 controller. It uses the internal 8MHz oscillator which
can be calibrated with the external 32kHz quartz.
amforth uses the serial connection (3pin connection on the left side) as it’s terminal.
amforth completely replaces the flash content. It overwrites the bootloader. You definitely need ISP or JTAG
to upload amforth to the controller. Afterwards the serial programming does not work anymore. You’ve been
warned!
A lot of useful code and examples how to use the various parts of the butterfly can be found at the wiki of
the German FIG Forth e.V. at www.forth-ev.de/wiki/doku.php/projects:avr:hilfsmittel. Basic Knowledge of the
German language is required.
The 32 kHz external quartz can be used to generate a timer tick. The following definition may help:
\ implement a timer with the 32kHz oszillator
decimal
\ timer/counter subsystem
182 constant ASSR
\ timer/counter2
179 constant OCR2A
178 constant TCNT2
176 constant TCCR2A
112 constant TIMSK2
75 constant GPIOR2
55 constant TIFR2
4 constant OC2addr
5 constant OVF2addr
variable tick
46 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
: -32kHz
\ Turn off interrupt
0 TIMSK2 c!
\ Turn off timer 2 asynchronous mode
ASSR c@
1 3 lshift invert and ASSR c!
;
Dallas 1-Wire devices use 1 wire (besides ground level) to connect a peripheral device with the hostmaster. A
common use case are the temperature sensors DS18[S|B]20. The communication protocol between the device and
the micro controller is simple but at some points very timing sensible.
The typical wiring is shown in the picture. The pull up resistor is recommended as well as the connection to VCC.
This recipe is based upon work from Brad Rodriguez for the 4C4th project. He split the 1-wire module into two
parts: a bit level layer for all the dirty, time critical work with only 2 small assembly words, and all other stuff
in portable forth code. Despite the fact, that he uses another controller type, the forth code remained almost the
same.
To use the 1-wire module new AmForth hexfiles have to be created with the file drivers/1wire.asm included
into your project master file (e.g. template.asm) All configuration is done with 2 constants that are set in the
same file. They define, which pin is connected to the 1-wire bus. There are no defaults
; Port and Pin for the 1-wire bus.
.equ OW_BIT=4
.equ OW_PORT=PORTE
.include "drivers/1wire.asm"
After burning the new system into the controller, two new words are available: 1w.reset and 1w.slot. The 1w.reset
reinitializes the 1-wire bus and gives a flag, whether at least one device is present or not. It would not make much
sense to continue, if no device is recognized.
: 1wirejob ... 1w.reset if
do-the-job
then ... ;
The 1w.slot writes the LSB to the 1-wire bus and reads one bit back, if a 1 was written. It turns off all interrupts
for approx 60 microseconds to achieve the correct timing. The lower byte of the TOS is rotated so repeated calls to
1w.slot can transfer all bits of a bytes without further code. It is probably the smartest word of the whole package.
: 1w.touch ( c1 -- c2 )
1w.slot 1w.slot 1w.slot 1w.slot
1w.slot 1w.slot 1w.slot 1w.slot ;
1-Wire Tools
The first useful tool is the low level 1w.reset. It checks whether at least one 1-wire device is present and working
or not. Other useful tools are in the file 1wire.frt. They perform a ROM search to print all ROM id’s of the
connected devices.
(ATmega1280)> hex 1w.showids
10 11 E5 68 2 8 0 2A
28 4C 75 CC 2 0 0 CD
ok
(ATmega1280)>
Code specialized for temperature sensors is in the file 1wire-temp.frt. Keep in mind, that at least 2 different
sensor types are available with different result encoding’s. The code is not currently capable to take care of the
differences.
> hex create sensor2 28 , 4C , 75 , CC , 2 , 0 , 0 , CD ,
ok
> decimal sensor2 1w.convert 750 ms sensor2 readtemp temp>pad pad count type
18.0 ok
>
Possible Improvements
The module opens the door to the 1-wire world. It is by far not complete or finished. Some things could (or
should?) be done better. Feel free to improve them and share them, please.
48 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
Atmegas have digital ports each with 8 individual pins. They that can be configured as input and output pins. To
make an easy use of them, amforth has a small library bitnames.frt in the lib directory.
The name port indicate that only IO ports can be used with this library. Since the addresses used are RAM
addresses, the whole address can be used, not only the IO range. The addresses are accessed on byte level. For
single bits (portpin: definitions) the bitnumber can exceed the 8 bits a byte can hold. In this case the address is
increased to a value that contains the bit specified: e.g. bit 24 of address 80 is the same as bit 0 of address 83.
Bitmaps are bound to the byte they address.
PORTB 1 portpin: led
Output pins
The simplest hardware is a LED connected to one pin. The following sequence initializes the pin and turns the
LED on:
> PORTB 1 portpin: led
ok
> led is_output
ok
> led low
Input pins
Input pins are used to get the voltage state: High or Low. A simple hardware would be as follows:
VCC
^
|
+-+
| |
| |
+-+
| |
0 +-----------+
| |
\
Port \
+ \>
|
|
----
GND
The resistor is not really needed, the pin can be configured to use an internal resistor.
> PORTB 0 portpin: mykey
ok
> mykey is_input
ok
> mykey pin_pullup_on
If the key is not pressed, the resistor (either the internal pull up or the external resistor) drives the voltage to high.
If you read the pin, you will get a 1 in this example:
> mykey pin@ .
1 ok
if the key gets pressed, it will connect the controller pin with ground level, giving a 0
> mykey pin@ .
0 ok
Bit Pattern
The pin! command changes the bits to the value given only for those bits which are set to 1 in the bitmask. In this
example, only the lower 4 bits are changed, the upper ones are left unchanged:
+-----+-----+-----+-----+-----+-----+-----+-----+
| 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 |
+-----+-----+-----+-----+-----+-----+-----+-----+
+-----+-----+-----+-----+-----+-----+-----+-----+
| 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 |
+-----+-----+-----+-----+-----+-----+-----+-----+
The same masking policy applies to pin@. Internally the portpin definition is converted into a bitmask. The words
high and low which set resp. clear the bitpositions are optimized versions of pin!:
: high $ff rot rot pin! ;
: low $00 rot rot pin! ;
Note: The extended bit range for single bits are available in amforth 5.3 or later. The file bitnames.frt works with
older version too.
50 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
4.2.3 EEPROM
This recipe is about the internal EEPROM storage of the atmega’s. It does not deal with external devices such as
I2C or SPI EEPROM chips.
The EEPROM contains usually 512 to 2048 bytes, depending on the actual controller type. The address range
goes from zero (0) upwards and is independent of the other memory regions flash and RAM. The address unit is
the byte, just like RAM. There is no alignment involved.
The usage pattern of the EEPROM is write seldom read often. which is slightly different from flash (write almost
never, read very often) and RAM (read and write very often). Any data written to EEPROM is kept over reset and
power off.
built in’s
The basic words to access EEPROM are @e and !e. They operate with the standard 2 bytes forth cells to read and
write data. There is no byte-level access.
2 @e u.
64 82 !e
Amforth uses EEPROM internally already. To keep track of the free memory area the command ehere gives the
first free EEPROM address.
> ehere u.
82 ok
>
The following commands manage EEPROM space: Edefer and Evalue. Evalue is works according to the
ANS94 standard word value.
> 1 Evalue one
ok
> one u.
1 ok
> 17 to one
ok
> one u.
17 ok
>
The Edefer word defines a word that, when called executes another word by its execution token. Amforth uses
this technique to implement the turnkey action.
To use EEPROM storage without Evalue or Edefer, the command ehere is the building block. It is the
pointer to the first unused byte in the EEPROM. It is itself a Evalue that can be adjusted with to to allocate (or
free) address space.
> : Eallot ehere + to ehere ;
ok
> ehere u. 17 Eallot ehere u.
84 101 ok
> 84 constant my-eeprom
ok
> my-eeprom u.
84 ok
>
Adjusting ehere as described above is consistent with later use of Evalue and Edefer.
Arrays
The recipes Defining and using Arrays and Values may give further ideas.
Note: Evalue was called simply value in revisions earlier than 5.3. Eallot was Ealloc and did leave the start
address of the allocated memory region.
The implementation of the first solution generates highly optimized machine code. The bitname solution is more
generic but significantly slower and is not interrupt safe.
: port:hi ( portadr bitno -- ) \ SBI
swap $20 - 3 lshift or $9A00 or code , end-code
;
Additionally some range checks should be applied to make sure that the instruction does actually work as it should
be
: _bitio
dup $1F U> if &-9 throw then
over $7 U> if &-9 throw then
;
52 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
The interrupt forth word is simply a colon word. It is executed within the context of the current user area and stack
frame. It must not have any stack effect outside the word. Using throw is not recommended since it will affect
the user area of the interrupted task.
\ TIMER_0 example
\
\ requires
\ in application master file
\ .set WANT_TIMER_COUNTER_0 = 1
\ from device.frt
\ TIMER0_OVFAddr
\ provides
\ timer0.tick -- increasing ticker
\
\ older mcu’s may need
\ TCCR0 constant TCCR0B
\ TIMSK constant TIMSK0
variable timer0.tick
: timer0.isr
1 timer0.tick +!
;
: timer0.init ( preload -- )
0 timer0.tick !
TCNT0 c! \ preload
[’] timer0.isr TIMER0_OVFAddr int!
;
: timer0.start
0 timer0.tick !
%00000011 TCCR0B c! \ prescaler 64
%00000001 TIMSK0 c! \ enable overflow interrupt
;
: timer0.stop
%00000000 TCCR0B c! \ stop timer
%00000000 TIMSK0 c! \ stop interrupt
;
All interrupts are available for forth interrupts. Versions earlier than 4.4 have the limitation that hardware interrupt
conditions could not be cleared.
int! (and friends) uses the interrupt address from the data sheet as an index, but points to a different address in
RAM.
Interrupts are processed in two stages. First stage is a simple low-level processing routine.
1. The low-level interrupt routine stores the index of the interrupt in a RAM cell (not directly accessible from
amforth).
2. Sets the T-flag in the status register to signal the inner interpreter that an interrupt needs attention.
The inner interpreter checks every time it is entered the T-flag. If it is set (1) the interrupt processing routine is
activated. It reads the number of the interrupt and calculates the index into the RAM based interrupt vector table.
This table is identical to the atmega interrupt table in the flash except that it holds the XT of the forth words that
will be started for the interrupt.
There are situations where no interrupts should be allowed. These code segments are usually named critical
sections.
: bar ." bar" int? . ;
: baz ." baz" int? . ;
: qux ." qux" int? . ;
If the standard interrupt enabled system setup is used, calling foo should print bar-1 baz0 qux-1. baz can
call words that use the critical[] word pair itself.
To temporarily turn off interrupts, the current state has to be stored. Since the critical section could be nested, a
global variable is not the best solution. The following code example stores the information on the return stack.
This requires some stack shuffling since a colon word is usually not allowed to manipulate the return stack outside
of its own scope. This is the reason, why the critical section must be paired within one definition afterwards.
Otherwise the return stack will have data that crashes the system.
\ global interrupt enable state as forth flag
: int? ( -- f )
SREG c@ SREG_I and 0> \ use the amforth-shell for the constants
;
: critical[ \ ( -- ) R( XT -- f XT )
r> int? >r >r \ keep the current state
-int
;
: ]critical \ ( -- ) R( f XT -- XT )
r> r> if +int then >r \ will crash if not matched
;
A possible modification is to add the PAUSE vector as well and turn off the cooperative multitasker during the
critical section.
: critical[ \ ( -- ) R( XT -- n*f XT )
r>
int? >r \ get the global interrupt flag
[’] pause defer@ >r \ get current multitasker
>r \ restore the returnstack
-int single
;
54 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
: ]critical \ ( -- ) R( n*f XT -- XT )
r>
r> [’] pause defer! \ restore multitasker
r> if +int then \ restore global interrupt flag
>r \ will crash if not matched
;
Atmegas have a seperate flash region called NRWW. This area is rather small (2 to 8 KB depending on the con-
troller type) and has some special features. It is primarily intended for boot loaders which can reprogram the RWW
flash without a special programmer. Amforth uses this feature to do the dictionary management. Furthermore the
NRWW section cannot self reprogram itself. Since this space is not available for user code amforth places as
much as possible of its predefined words there.
If an application needs code space in the NRWW section for some tasks, amforth has to leave room for it. This can
be done by setting the Assembler variable AMFORTH_RO_SEG to a value higher than NRWW_START_ADDR
(the default). This leaves the flash between NRWW_START_ADDR and AMFORTH_RO_SEG free for other
uses. The build process restructures the word placement accordingly.
Internally the files dict/nrww.inc and dict/rww.inc use predefined dictionary file sets to include the
essential words in a way to maximize the NRWW utilization.
The Serial Peripheral Interface is used for high-speed data exchange between the controller and some peripheral
devices. There are several modes available.
It consists of three signal lines plus one per peripheral device (called slave). All peripheral devices share the signal
lines and use the selecting line exclusivly. For any given data transfer only one of the selecting lines must be at
LOW level, all others must be HIGH.
The basic data transfer operation is a data exchange of 8 bits. The sender transmits 8 bits and recieves 8 other bits
in return from the communication partner.
The basic forth word is c!@spi which translates to character store/fetch via SPI. It uses the hardware SPI module
of the controller and thus the pre-defined pins of it.
Basic Workflow
The built-in SPI module uses a few pins to establish the communication with any device. To distinguish between
different SPI attached devices a separate signalling line is used: slave select. Every slave device is connected with
one such line. The SPI communication takes place with the one which signalling line is LOW. All other lines have
to be HIGH.
The setup of the slave select lines includes two steps: configuring as output and give it HIGH level when idle.
Note that a pin that is configured as output will immediatly go to LOW level. This may disturb a SPI slave so after
configuring the line direction the port has to go to HIGH exllicitly. When all slave select lines are configured, the
remaining SPI setup can take place
\ requires bitnames, quotations and spi loaded
> PORTB 0 portpin: dev.ss \ define hardware
> dev.ss to spi.ss \ assign ss pin to lib
> spi.ss is_output \ short LOW pulse
> spi.ss high \ de-select slave
> +spi \ turn on SPI module
Data Exchange
Any SPI transfer starts with pulling the slave select line LOW. Now any number of read/write operations may
take place. To stop an exchange, the slace select line goes back to HIGH. This signals the slave device that the
communication has ended and it usually goes back to a state that awaits a new commication.
The basic c!@spi is the building block for the next words
\ single byte transfers
: c@spi ( -- c ) 0 c!@spi ;
: c!cpi ( c -- ) c!@spi drop ;
The file core/words/n-spi.asm contains speed optimized implementations of the n@spi and n!spi
words.
SD-Cards/MMC
MMC and SD-Cards have an SPI mode which is slower than the usual mode used on PC’s but is simpler to
program.
\ standard stuff, only if not already uploaded
#require postpone.frt
#require marker.frt
#require bitnames.frt
\ board definitions
#include netio.frt
\ SPI library
#require quotations.frt
#require 2rvalue.frt
#include spi.frt
\ SD Card specific
#include mmc.frt
The include order of the file is important. The board specfic definitions need to define the words +spi, -spi for
global SPI port setup. In addition the commands +mmc and -mmc are used to perform a single communication
with the device. The portpin definitions are not used elsewhere but should match the hardware.
PORTB 0 portpin: sdcard
sdcard to spi.ss
: +mmc
sdcard low
;
: -mmc
sdcard high
;
56 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
After successfully loading these files, the command mmc_init initializes the communication and enables the
remaining access. It has to be issued every time the card has changed.
(ATmega640)> mmc_init
ok
(ATmega640)> mmc_CID . cr 10 0 mmc.
0
1 50 41 53 30 32 47 46 12 39 B6 28 D6 0 B4 99 ok
(ATmega640)>
4.2.9 Telnet
Hardware
Telnet is a TCP/IP protocol. It requires a connection to a network (RJ45, twisted pair Ethernet is commonly used).
A microcontroller like the Atmega needs a separate module to deal with all the low level stuff. For this recipe an
ENC28J60 is used. It is connected via SPI to the Atmega. In addition, the interrupt line of the enc28j60 (pin4) has
to be connected to INT2 (pin3 on an Atmega644).
The software needs slightly more that 1KB RAM for itself (mostly buffers), so only the bigger types satisfy this
requirement.
Software
The code assumes a /24 network for the other network settings.
Using
After uploading the code base (4th_mod1.frt includes most of the dependencies) the serial port is still the
command prompt. With the command +telnet the network is started and the TCP Port 23 is opened. In this
stage, a network ping should work
serial terminal:
|> +telnet
| ok
|> Send Ping Reply !
|Send Ping Reply !
|Send Ping Reply !
|Send Ping Reply !
remote shell:
|$ ping 192.168.2.79
|PING 192.168.2.79 (192.168.2.79) 56(84) bytes of data.
|64 bytes from 192.168.2.79: icmp_seq=1 ttl=64 time=1037 ms
|64 bytes from 192.168.2.79: icmp_seq=2 ttl=64 time=75.1 ms
|64 bytes from 192.168.2.79: icmp_seq=3 ttl=64 time=19.0 ms
|64 bytes from 192.168.2.79: icmp_seq=4 ttl=64 time=19.0 ms
|64 bytes from 192.168.2.79: icmp_seq=5 ttl=64 time=19.0 ms
To get a telnet session, the amforth command interpreter has to switch its IO
serial terminal:
|> +telnet
| ok
|> +tnredir
remote shell:
$ telnet 192.168.2.79
|Trying 192.168.2.79...
|Connected to 192.168.2.79.
|Escape character is ’^]’.
|Start Telnet Server:
| ok
|> 1 2 + .
|1 2 + .
|3 ok
|>
| ok
|> : hi ." Howdy, mate!" cr ;
|: hi ." Howdy, mate!" cr ;
| ok
|>
| ok
|> hi
|hi
|Howdy, mate!
| ok
|>
| ok
|> -tnredir
|-tnredir
serial terminal:
|Stop Telnet Server ! ok
|> hi
|Howdy, mate!
| ok
|>
4.2.10 Timer
The timer library in the lib/hardware directory consists basically of two parts: an access module and a generic
module that depends on one of the access modules.
The access module (in timer0.frt and timer1.frt) encapsulate the access to the selected timer. It uses
interrupts to create a millisecond counter for common usage. This millsecond counter is a single cell variable that
gets continuesly incremented and wraps around every 65,5 seconds.
The generic routines rely on this counter. A timer is simply a single cell number that is either the starting value of
the millisecond counter (e.g. for elapsed) or the stop value (after).
To get a timer get the current value of the tick. With that number you can call elapsed to get the number of
58 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
milliseconds since start. To check whether a timer is expired you need to calculate the end time by adding the time
span to the current tick value. The word expired? compares the current tick value with that calculated time and
leaves a flag.
The words are multitasker friendly (by calling pause whenever useful. The words provided so far are
• expired? (t – flag) checks whether a timers has expired. calls pause internally.
• elapsed (t – n ) gets the number of milliseconds since the timer has started.
• ms ( u – ) alternative implementation of standard word ms
• after (XT u –) waits u milliseconds and executes XT afterwards.
• every (XT n – ) executes XT every n milliseconds. The XT has the stack effect ( – f) for f beeing a flag
indicating whether or not terminating the every loop.
An usage example is the Loop With Timeout. It is used as an replacement for begin, and takes an number as the
amount of milliseconds the loop has to finish, otherwise an exception is thrown.
The Two Wire Interface connects peripheral devices with the controller. It is compatible with the I2C bus so any
I2C device can be connected to the TWI. The bus consits of 2 signal lines. Every device has an address. Multiple
devices can be connected as long as they use different addresses. Most I2C devices use jumpers to select from a
(usually short) list of possible devices, limiting the number of identical devices.
Most client devices want a clock speed of 100 kHz or 400 kHz. The bitrate register should be well above 10 if the
controller is the bus master. The calculation formula
𝑐𝑝𝑢𝑐𝑙𝑜𝑐𝑘
𝑡𝑤𝑖𝑐𝑙𝑜𝑐𝑘 =
16 + 2 * 𝑏𝑖𝑡𝑟𝑎𝑡𝑒𝑟𝑒𝑔𝑖𝑠𝑡𝑒𝑟 * 4𝑝𝑟𝑒𝑠𝑐𝑎𝑙𝑒𝑟
The next table shows the resulting twi clocks for a 8MHz device clock
bitrate register (may be any value between 0 and 255)
prescaler
4 8 16 32 64 128 255
0 333.333 250.000 166.667 100.000 55.556 29.412 15.209
1 166.667 100.000 55.556 29.412 15.152 7.692 3.891
2 55.556 29.412 15.152 7.692 3.876 1.946 978
3 15.152 7.692 3.876 1.946 975 488 245
The symbol naming is based upon Atmel’s naming conventions. If the controller has only one usart module, it is
named either usart or usart0. Newer Atmegas use the 0 regardless of the real number of usart modules, older
ones omit the 0 completely. You definitely have to check the datasheet.
The following controllers use the old schema, they need the usart file:
8515def.inc:.equ RXEN = 4 ; Receiver Enable
8535def.inc:.equ RXEN = 4 ; Receiver Enable
m103def.inc:.equ RXEN = 4 ; Receiver Enable
m163def.inc:.equ RXEN = 4 ; Receiver Enable
m16Adef.inc:.equ RXEN = 4 ; Receiver Enable
4.2.13 Watchdog
The watchdog is a build-in module present in all atmega controllers. It triggers a reset if for a predefined period
of time nothing is done to prevent it.
The controller has a special machine instruction for the watchdog reset called wdr. Amforth has a wrapper forth
word with the same name after including the file core/words/wdr.asm.
This word needs to be called often enough to keep the watchdog from resetting the controller. For a system that
basically waits at the command prompt the pause command could be sufficient:
> ’ wdr is pause
Another potential place for adding a wdr is the inner interpreter by either changing
amforth-interpreter.asm or the core/words/exit.asm. Adding the (machine) wdr instruc-
tion there makes sure that the watchdog is reset as long as the inner interpreter works.
Initialization
Early atmega variants need to initialize the watchdog every time after a reset, newer ones keep it active even over
resets. This may cause troubles since the WDR needs to be called much earlier for these controllers. One solution
is to place the WDR activation at the beginning of the turnkey actions.
Acknowledgements
60 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
This creates the dictionary entry named my-array and allocates 42 cells in RAM. BUT: the my-array dictionary
entry is not connected to the allocated RAM. The correct solution is:
variable my-array 42 cells allot
This makes the dictionary entry named my-array, sets up the link to the RAM address and allocates an additional
amount of 42 cells in RAM.
Forth 200x introduced a new word named Buffer:. With it the above code turns into
43 buffer: my-array
please note the different sizes! The buffer:-implementation allocates the exact number of bytes whereas the
variable version adds the given size parameter to the 1 cell it allocates anyways.
The use of the array is quite simple:
: my-array-@ cells my-array + @ ;
: my-array-! cells my-array + ! ;
Arrays of structures
This example uses structures. Structures can be used after including of the structures.frt file. First a hash
data structure consisting of two elements is defined. This structure is used to create an array of a few elements
afterwards.
begin-structure hash
field: hash.key
field: hash.value
end-structure
\ inspired by CELLS
\ ( n -- size )
\ calculates the size of n items of the
\ type hash
: hash-cells hash * ;
\ define a hash-array
: hash:
hash-cells buffer:
does>
swap hash-cells +
;
The helper word hash-cells calculates the size of the data structure in terms of byes, just like the standard
word cells does it.
Now we’re using the words (using the amforth-shell). First define an array of 4 hash pairs. After that store a
key/value pair at a particular position and retrieve it again later.
(ATmega16)> 4 hash: my-hash
ok
(ATmega16)> 42 3 my-hash hash.key !
ok
(ATmega16)> 4711 3 my-hash hash.value !
ok
(ATmega16)> 3 my-hash hash.key @ .
42 ok
(ATmega16)> 3 my-hash hash.value @ .
4711 ok
(ATmega16)>
If you place the data structure in a different memory (e.g. the EEPROM) adapt the code accordingly. buffer:
needs to be replaced with a similiar allocation word and @/! with the proper memory access words. Remember,
memory is not always 2 bytes per cell.
See also:
Structures
This code does not work as expected. The value compiled with , is compiled into the dictionary, which is read
using the @i word. The correct code is
: const create , does> @i ;
Deferred words a technique that allows to change the behaviour of a word at runtime. This is done by storing an
execution token under a certain name that is executed whenever that name is called. The stack effect is entirely
that of the stored execution token code. The basic specification is at www.forth200x.org/deferred.html which is a
must-read now.
AmForth has 3 different variants of defer which differ in the place, where the execution is stored: Edefer stores
in EEPROM, Rdefer stores in RAM and Udefer stores in the USER area. The definition of a deferred word does
not set a useful execution token. Using a deferred word without giving it a XT will crash the system. After the
definition of the words, the further handling is always the same: IS stores the execution token into the deferred
word. Further the standard words defer@ and defer! read and write the execution token regardless of the exact
storage location.
AmForth uses the deferred words technique already internally:
• turnkey is an EEPROM based deferred word that is executed from QUIT usually during startup and reset.
• the words key, key?, emit, and emit? are USER deferred words for low level terminal IO.
• refill and source are USER deferred words used by the forth interpreter to get the next command line.
• pause is a RAM based deferred word that is called whenever a task switch can be done.
• !i does the actual flash write of a single cell. It is intended for Unbreakable AmForth
Since there is no standard defer word, the programmer has to take care where to store the execution tokens. An
EEPROM location is keept over resets/restarts and is valid without further initialization. A USER based deferred
word can be targeted to different words in a multitasking environment and finally a RAM based deferred word can
be changed frequently. To get a standard however, the following definition may be helpful:
62 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
Sealing Defers
It is sometimes necessairy to prevent a deferred word from changing. This can be achieved with the following
word
: defer:seal ( XT -- )
dup defer@ ( -- XT’ XT )
swap ( -- XT XT’)
dup [’] quit @i ( get DO_COLON) swap !i
1+ dup rot swap !i
1+ [’] exit swap !i
;
With it, the dictionary entry is patched directly to change it from beeing a defer to a colon word named as the
deferred word calling only the current XT stored in it
(ATmega32)> Edefer mytest
ok
(ATmega32)> ’ ver is mytest
ok
(ATmega32)> mytest
amforth 5.3 ATmega32 ok
(ATmega32)> ’ mytest 5 - 10 idump
10E0 - FF06 796D 6574 7473 10CB 0836 005C 07D6 ..mytest..6.\...
10E8 - 07E0 FFFF ...
ok
(ATmega32)> ’ mytest defer:seal
ok
(ATmega32)> ’ mytest 5 - 10 idump
10E0 - FF06 796D 6574 7473 10CB 3800 078C 381A ..mytest...8...8
10E8 - 07E0 FFFF ...
ok
(ATmega32)> mytest
amforth 5.3 ATmega32 ok
(ATmega32)>
Technically the word mytest is changed to the same dictionary content as if it was defined as
: mytest ver ;
This is possible since a deferred word occupies 3 flash cells in the body and the faked colon definition needs only
2: the XT of the deferred word and the exit call.
Sometimes it may be desirable to turn off the echo function in accept when entering commands. One solution to
do it is to temporarily redirect the emit to do nothing.
variable tmpemit
: -emit [’] emit defer@ tmpemit !
[’] drop is emit ;
: +emit tmpemit @ is emit ;
> 1 2 3
ok
> .s
0 809 3
1 80B 2
2 80D 1
ok
> -emit .s +emit
ok
>
Macros are small code snippets that do not represent a colon word for itself but the code is used verbatim in other
definitions. To use them, include the file lib/macro.frt (requires evaluate.frt and amforth version
4.7ff)
> macro square " dup *"
ok
> : foo 5 square . ;
ok
> foo
25 ok
There is only one drawback: the macro string cannot contain the delimiting character itself. You’re free to choose
any character however
> macro square2 _ dup *_
ok
> 5 square2 .
25 ok
>
4.3.6 Multitasking
Multitasking is a way to execute separate chunks of program code (tasks) apparently simultaneous on a single
CPU. Of course, the separate tasks will run one after another. If the CPU can switch between them fast enough,
separate tasks may appear to execute in parallel.
Multitasking in amforth is achieved as cooperative multitasking 1 : In every task the programmer defines places,
where control is given up, such that the next task can run. The tasks current state is stored in a piece of memory
called the task control block (TCB). TCBs are organized in a simple, linked list and are visited in round–robin
fashion.
1 as opposed to preemptive multitasking
64 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
What is a Task?
Every task owns a piece of RAM, where it finds a set of runtime information (user area) and where it has its own
space for the data and return stacks. This space is called a task control block (TCB). Is is referred to by the task id
or tid, which happens to be the start address of the TCB by convention[#]_
The runtime information includes:
• status, whether the task is awake or sleeping
• follower, points to the next task in the list
• where do the stacks start and how many entries are currently on them
• the current value of base
• pointers to deferred words such as key, emit and the like
• the content of the stacks is not regarded part of the task control block. They can be located anywhere as
long as their location is known. They do belong to the task, however.
Viewed from afar, a task is just a piece of RAM holding a small set of important information.
Switching Tasks
To switch execution from one task to the next, the following things need to happen somehow:
• store the relevant bits of the current runtime in the task control block (stack pointers, mainly)
• look up the next task’s control block
• switch the userarea-pointer to that control block
• unfold the same bits, which were stored before giving up control, back into the runtime
• resume execution at the next instruction of the new task
So the problem is mainly an exercise in saving and restoring all relevant information.
Problem
Solution
Include the file lib/multitask.frt in your programm, define separate tasks as separate words. The start of
everything needs a little extra code (see starttasker). This solution is working together with turnkey.
Sample Program
When the program is started, LEDs connected to PORTB will blink. However, the prompt is presented as well
and commands will be handled.
> run-turnkey
amforth 4.7 ATmega32
ok
> tasks
149
running
309
running
Multitasker is running ok
> N @ .
199 ok
>
66 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
Discussion
The two tasks will happily run along provided, that both tasks call pause regularly. This call is built into the
command loop already. It is possible to call run-turnkey as turnkey. The program will survive a power cycle,
because task: stores the neccessary information in flash memory:
1. the address of the task control block
2. the start of the data stack (sp0)
3. the start of the return stack (rp0)
The sizes of the stacks are not explicitly stored. They can be inferred from the knowledge that all space is allocated
as one chunk. However, amforth does not protect the stack from overflows. Exceeding the allocated stack space
does cause unexpected crashes of your programm (see below at task:).
task-init prepares the task control block located in RAM. It erases any previous content, stores the addresses of
the stacks, the top–of–stack address for the data stack, base, and the status of the task (sleeping). start-demo adds
the calls to the tasks body into the TCB and stack space. task: will use three entries from the stack.
1. additional size of the user area in this task. This space can be used to create user–variables, which belong
to this task only.
2. size of the task’s return stack
3. size of the task’s data stack. Both stack sizes may be as small as $20 bytes. However, programs
exceeding a certain complexity may experience inexplicable crashes. If the program works in the foreground but
not as a task, increasing the stack sizes may help. Please note that calling ms, which in turn calls 1ms will not
produce accurate time intervals any more, depending on how much time is spent in the other tasks. One might
argue that the startup sequence (starttasker) is way too long and should not be handled by the programmer. On the
other hand, full control over the startup might be useful in unforeseen ways.
amforth ships the file lib/multitask.frt featuring a multitasker based on code by Brad Eckert.
The layout of the task control block is fixed. Technically it is located at the start of the so called User Area. The
first 6 entries (status ... handler) are not intended for changes by the programmer. The next 6 entries (base ... /key)
are commonly changed by the programmer. If more space for user variables is desired, the user area needs to be
increased specifically. When defining user variables, the offset of that variable from the start of the user area needs
to be specified. It is the programmers duty to keep track of how many entries have been used.
Also as a consequence the tid of a task holds the start address of the user area for that task. Its value is copied
into the user pointer upon task switch. The user pointer is fetched and stored with up@ and up!, respectively (see
definition of wake below).
Two offsets into the TCB are defined as user variables. They produce the address of TCB[0] and TCB[2] respec-
tively, correctly using the current TCB’s address.
decimal
0 user status
2 user follower
After that two noname: words are defined. These words will not have a header in the vocabulary, their execution
tokens (xts) are stored in the constants pass and wake. Their values will be stored in the status field (TCB[0]).
:noname ( ’status1 -- ’status2 )
cell+ @ dup @ 1+ >r
; constant pass
:noname ( ’status1 -- )
To switch between tasks the deferred word pause is used. Normally, pause does nothing. Therefore turning
multitasking off is simple:
\ stop multitasking
: single ( -- )
[’] noop is pause
;
A new word multitaskpause is defined, which will switch from this to the next task.
\ switch to the next task in the list
: multitaskpause ( -- )
rp@ sp@ sp ! follower @ dup @ 1+ >r
;
\ start multitasking
: multi ( -- )
[’] multitaskpause is pause
;
multitaskpause looks short and innocent, but a little explanation is called for:
rp@ \ -- rp | fetch the current return stack pointer
sp@ \ -- rp sp | fetch the current data stack pointer TOS
sp \ -- rp sp tcb[sp] | get the addr of user variable to store TOS
! \ -- rp | store, TCB[8] := TOS
follower \ -- rp tcb[2] | get the address of TCB[2]
@ \ -- rp tid’ | fetch it’s content, tid of the next task
dup @ \ -- rp tid’ status’ | fetch status of the next task (xt)
1+ \ -- rp tid’ pfa | xt \Verb|>body|
>r \ -- rp tid’ | put pfa of pass or wake on the returnstack
When multitaskpause exits, the interpreter finds the xt of wake or pass on the return stack and will continue
execution there.
If status was pass, the next task is sleeping, so we need to look for the next next task:
\ -- rp tid’ | these are still on the stack
cell+ \ -- rp tid’[2] | point to follower
@ \ -- rp tid’’ | get the tid of the next next task
dup \ -- rp tid’’ tid’’ |
@ \ -- rp tid’’ status’’ | fetch status of next next task (xt)
1+ \ -- rp tid’’ pfa | xt of >body
>r \ -- rp tid’’ | put xt of next next tasks status on return stack
This is repeated until an awake task is found. If status was wake, the next task should be running, so we need to
unfold it:
\ -- rp tid’ these are still on the stack
up! \ -- rp make user pointer point to tid’
This was the magic line. Now the stacks are different stacks! We left the old task’s data stack behind with rp on
top. Now we look at the new task’s stack and find rp’ of that task on top of it.
sp \ -- rp’
\ -- rp’ tid’[sp] get addr of TOS locationMultitasking
@ \ -- rp’ sp’ retrieve stack pointer of now current task
sp! \ -- rp’ store it in (activate) stack pointer
rp! \ -- store rp’ of this task in current rp
68 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
Switching multitasking on is simply pointing pause to multitaskpause. The inner workings are far from obvious,
but they have been proven to work.
Handling tasks
A little more tricky is setting up a piece of code to be run in a task. activate will be used in a snippet similar to
this.
: run-demo ( interesting work here ... ) ;
$20 $20 0 task: task_demo
\ create task, allot tcb + stack space
: start-demo
task_demo tcb>tid activate
\ words after this line are run in new task
run-demo
;
activate will store the xt of run-demo on the return stack belonging to the TCB. It will also save the address of top
of return stack on top of the data stack belonging to the same TCB, and the address of TOS in the field TCB[sp].
This particular order of information is expected by wake.
: cell- negate cell+ negate ;
\ continue the code as a task in a predefined tcb
: activate ( tid -- )
dup
6 + @ cell-
over
4 + @ cell- ( -- tid sp rp )
\ point to RP0 SP0
r> over 1+ !
( save entry at rp ) \ skip all after ACTIVATE
over !
( save rp at sp )
\ save stack context for WAKE
over 8 + !
( save sp in tos )
task-awake
;
onlytask initializes the linked list with the current task only. It copies the tid of the current task into the field
TCB[follower] to create a circular list.
\ initialize the multitasker with the current task only
: onlytask ( -- )
wake status !
\ own status is running
up@ follower ! \ point to myself
;
alsotask links a new task given by its tid into the list behind the current task.
: alsotask ( tid -- )
[’] pause defer@ >r \ stop multitasking
single
follower @ ( -- tid f)
over ( -- tid f tid )
follower ! ( -- tid f )
swap cell+ ( -- f tid-f )
!
r> is pause \ restore multitasking
;
And then there is tasks to print the tid of every task in the list and its state to the serial console. It will also report,
whether the multitasker is switched on or not. If you uncomment the three commented lines, then the values
of top–of–stack and start–of–stack for the data and return stacks are also printed out. This might be useful for
debugging.
: tasks ( -- )
status ( -- tid ) \ starting value
dup
begin
( -- tid1 ctid )
dup u. ( -- tid1 ctid )
dup @ ( -- tid1 ctid status )
dup wake = if ." running" drop else
pass = if ." sleeping" else
abort" unknown" then
then
\ dup 4 + @ ." rp0=" dup u. cell- @ ." TOR=" u.
\ dup 6 + @ ." sp0=" dup u. cell- @ ." TOS=" u.
\ dup 8 + @ ." sp=" u. cr
cell+ @ ( -- tid1 next-tid )
over over = ( -- f flag)
until
drop drop
." Multitasker is "
[’] pause defer@ [’] noop = if ." not " then
." running"
;
Creating a TCB
So there is only one thing left to do, namely create space for a TCB and the stacks.
: task: ( C: dstacksize rstacksize add.usersize "name" -- )
( R: -- addr )
create here ,
\ store address of TCB
( add.usersize ) &24 + allot \ default user area size
\ allocate stacks
( rstacksize ) allot here , \ store sp0
( dstacksize ) allot here , \ store rp0Multitasking
1 allot \ keep here away, amforth specific
does>
\ leave flash addr on stack
;
: tcb>tid ( f -- tid ) @i ;
: tcb>sp0 ( f -- sp0 ) 1+ @i ;
: tcb>rp0 ( f -- rp0 ) 2 + @i ;
: tcb>size ( f -- size )
dup tcb>tid swap tcb>rp0 1+ swap -
;
70 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
task: allots memory for the task control block and its associated stacks. The sizes of the stacks are taken from
the data stack. The start of the data stack (SP0) is stored in TCB[6], the start of the return stack (RP0) is stored
in TCB[4]. Then new tid is moved from the return stack to the data stack. The task is marked as sleeping and
one more byte is allot’ed to keep here out of the way. This is an implementation feature of amforth. Also please
note that stacks are growing downwards. task-init initializes a TCB and copies the information stored in flash into
their correct locations.
: task-init ( f -- )
dup tcb>tid over tcb>size 0 fill \ clear RAM for tcb and stacks
\ fixme: possibly use init-user?
dup tcb>sp0 over tcb>tid &6 + !
\ store sp0 in tid[6]
dup tcb>sp0 cell- over tcb>tid &8 + ! \ store sp0-- in tid[8], tos
dup tcb>rp0 over tcb>tid &4 + !
\ store rp0 in tid[4]
&10 over tcb>tid &12 + !
\ store base in tid[12]
tcb>tid task-sleep
\ store ’pass’ in tid[0]
;
Versions of lib/multitask.frt prior to amforth-4.7 are broken in that there is no permanent storage as
described above. These versions of the multitasker work, but they do not survive a power cycle.
The Atmegas have a number of power saving options. All of them are available with the sleep instruction. Amforth
has a wrapper word with the same name which works on newer atmegas only. You can simply include the file
words/sleep.asm into your dict_appl.inc file and try assembling. If it does not produce an error, the
sleep instruction can be used.
The next step is a system that uses interrupt driven terminal IO and possibly other interrupt sources. This makes it
possible to include the sleep call into the pause deferred word.
: mypause 0 sleep ; ’ mypause is pause
The exact meaning of the parameter (0) should be checked with the data sheet. Also make sure, that the interrupts
are working properly. Otherwise the controller will sleep until the reset button is pressed..
4.3.8 Redirect IO
The IO system consists of 4 words: EMIT, EMIT?, KEY and KEY?. The are deferred words, e.g. they can be
changed at runtime.
Output
Amforth has many words like ." and type to write information. All these words do not do the output work
actually, they call emit for each and every single character.
: morse-emit ( c -- )
... \ some code to let a buzzer beep for the character c
;
’ morse-emit is emit
\ now everything gets morsed out. even the prompt
\ unless your morse-emit does not call the previous
\ emit nothing will be displeyd
s" let it beep" type
The same technique may be used for e.g. a 44780 LCD. The new code has to take care of everything like scrolling
etc as well.
To complete the picture, another word emit? should be redefined. It is called in front of <emit> to check whether
the output is possible. If no such check is necessary or possible, just do an ’ true is emit?
Unless you do not change the turnkey action as well, everything gets reset to serial IO whenever you call WARM.
Input
Input is based upon single characters. The command key? checks whether an unread character is available and
key fetches it. To read an user supplied buffer, the command accept can be used. It reads until either the buffer is
filled or an end-of-line character is found (caridge return and/or line feed).
Depending on the input source, different strategies may be used. The simplest way is to poll the input device
frequently and hope that no character is lost. More sophisticated is the use of interrupts. They can be called at any
time and almost guarantee that no characters will be lost. The interrupt usually fills an internal small buffer key
and key? can deal with.
: ps2-key-isr ( -- )
\ get the most recent key stroke
\ place the key-event in a queue
;
: ps2-key? ( -- f )
\ check the input queue, return true if
\ a key-event is unread
;
: ps2-key ( -- c )
\ read and unqueue the oldest key-event from the
\ queue.
;
\ the next word changes the terminal input to
\ the PS2 based system. This cannot be done interactivly!
: ps2-init ( -- )
\ initialize ps2-key-isr
[’] ps2-key? is key?
[’] ps2-key is key
;
72 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
If the controller constantly resets and prints only (part of) the version string, it could be really nice to know
why it behaves that way. The controller itself stores the reset reason in the machine control register, which gets
unfortunately overwritten real soon. amforth reads its content upon startup into an unused register for later usage
however.
Adding the following few lines to the applturnkey.asm file prints the numeric information at every reset
; print the numeric reason for reset
; forth code: 10 c@ . cr
.dw XT_DOLITERAL
.dw 10
.dw XT_CFETCH
.dw XT_DOT
.dw XT_CR
The following screen shows the program output after power on reset (4), pressing the reset button (2) and an
ordinary call to cold:
-- power on --
> 4
amforth 4.7 ATmega328P 16000 kHz
-- pressing reset button --
> 2
amforth 4.7 ATmega328P 16000 kHz
> cold
0
amforth 4.7 ATmega328P 16000 kHz
>
The exact meaning of the numbers is available by reading the respective controller data sheet (8 usually means
watch dog reset).
On the command line, strings are part of the current SOURCE buffer. Their content is usually lost, when SOURCE
gets REFILL’ed. The command
> s" hi there" type
hi there ok
>
works fine. If you split the commands into two lines like
> s" hi there"
ok
> type
ei there ok
>
it will print the last character of type and the remaining characters from the previous command line. If a string
has to be used later on, it needs to be moved to another buffer within the same command line or accept is used
to enter the string into some other buffer (see below for an example).
> s" hi there" pad swap cmove> \ length information gets lost
ok
> pad 7 type
hi ther ok
>
In colon definitions, s” does something completely different: It copies the whole string from the SOURCE buffer
to flash (into the dictionary) and at runtime provides the flash address and length of the string. This data can be
used with e.g. ITYPE.
Notes
1. s" compiles a string into flash. The compiled string gets a runtime that leaves the address/length pair of
the compiled string and skips its content for further program execution.
2. Places a double cell zero value onto the stack to be used at >number.
3. pad is a commonly used temporary storage pool. It is not used by the system itself. Its location is rela-
tive to HERE, so every change to HERE will move PAD as well.
4. >number is a standard word that converts a string to a number. To get the actual age (assuming a rea-
sonable value) the 2drop removes some returned data. Finally the double cell age is converted to
single cell and stored at the variable age.
5. getname leaves the actual length of the name string on the stack. This length information is not stored
elsewhere. .name removes this information so you cannot reconstruct this data.
> : label: create s, , does> ;
ok
> 42 s" hello" label: example
ok
> example icount itype
hello ok
> example icount 2/ 1+ + @i .
74 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
42 ok
>
s, copies a string from RAM to flash, increasing the DP. The storage format follows the counted string schema:
first cell is the length information, followed by the characters, 2 per flash cell. A zero byte is appended if necessary
to fill the last flash cell. It is an internal factor of s".
This recipe is based upon ideas from Hannu Vuolasaho and Michael Kalus.
4.3.11 Structures
Structures are used to keep complex data in one place. Classical use cases are records.
To use structures, load the file lib/forth200x/structure.frt into the controller. It has no further de-
pendencies.
\ simple test example for forth200x structures
\ define a new data structure named list.
begin-structure list
field: l.p \ previous
field: l.n \ next
field: l.d \ data
end-structure
Many low level routines require to wait for a specific condition come true: A transmission is finished, a flag is
set etc. Most of the time these action do work fine. But sometimes, the check loop does not terminate for some
(usually stupid) reason and the program essentially crashed.
\ wait for twi finish
: twi.wait ( -- )
begin
TWCR c@ 80 and
until
;
To circumvent such unwanted endless loops, a timeout is often a solution. This ensures that the loop will be left,
regardless what happens. This recipe is based upon the timer module from the lib/hardware directory, that
provides a millisecond tick that can be used for timeouts as well.
A timeout loop is basically a modified begin that takes a runtime parameter: the maximum allowed time for a
particular loop. The loop terminater (again, until, etc) is left unchanged. If the loop terminates properly, the
timeout is ignored, otherwise an exception is thrown. It is up to the programmer to catch that exception. If it is not
catched, the forth interpreter will do it and returns to the command prompt.
\ timeout-begin is a potentially endless loop
\ that terminates after a predefined timeout
: (check-alarm)
alarmtime @ expired? if -512 throw then
;
: timeout-begin
postpone (init-alarm)
postpone begin
postpone (check-alarm)
; immediate
Since the alarm checks are simple, some precautions should be obeyed:
• The timer gives a millisecond resolution.
• The longest timeout period is 65.535 seconds (slightly more than a minute).
• The timeout-loop cannot be nested. If you want to use it in a multitasking environment, change the
variable to a user.
• Don’t forget to initialize and start the timer.
\ testcase. timeout after 100ms
: foo
100 timeout-begin
noop
again
;
Turnkey application automatically execute a word upon startup. The default turnkey action establishes the serial
line communication and prints the welcome messages (version number, cpu name, frequency). When the turnkey
action finishes, the control is handed over to the amforth interpreter loop, which never finishes.
76 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
Turnkey itself is a deferred word. That means that it can be changed by applying a new execution to it. Whether
the turnkey action leaves data on the stack is up to the application needs. Turnkey is called with an empty data
stack.
: myinit ( -- )
\ some code
;
Special care must be taken if the turnkey action should not be replaced but appended. To achieve this, the current
turnkey action has to be stored elsewhere and this execution must be called inside the new turnkey command.
variable oldturnkey
’ turnkey defer@ oldturnkey !
: myturnkey
oldturnkey @ execute
my_own_turnkey_actions
;
’ myturnkey is turnkey
Be aware that the initialization sequence must not be repeated, this will create an endless loop by calling the
turnkey action inside itself.
The amforth-shell.py from Keith Amidon may simplify the interaction with amforth and the forth code manage-
ment while uploading projects.
It is a python2 script that runs fine on Linux, other platforms may work as well. The tool takes care of the correct
transfer of the source code and will optionally pre-process the sources: e.g. replace the register names with their
numeric values. This saves valuable flash (dictionary) space since most of these registers are used only once.
mt@ayla:~/amforth$ cat tools/test.frt
\ this is a test
INT1Addr .
ver 1000 ms cr
1000 ms
ver cr ver
1000 ms
mt@ayla:alias|grep amforth-shell
alias u0=’$HOME/amforth/tools/amforth-shell.py -p /dev/ttyUSB0 --no-error-on-output’
mt@ayla:~/amforth$ u0 test.frt
|I=mcudef
|I=using device.py for atmega1280
|F=....test.frt
|C| 1|\ this is a test
|S| 2|INT1Addr .
|O| 2|4
|S| 3|ver 1000 ms cr
|O| 3|amforth 4.9 ATmega1280
|S| 4|1000 ms
|S| 5|ver cr ver
|O| 5|amforth 4.9 ATmega1280
|O| 5|amforth 4.9 ATmega1280
Note the replacement of the INT1Addr with 4 in line 2. This is done by using the device.py file from the
core/devices/atmega1280p directory which is automagically identified and loaded at startup. And second note,
that the file is found automatically in the subdirectory tools.
The amforth-shell.py utility has a lot of more features: an interactive command prompt with dynamic command
completion and command history (stored across multiple invocations), a lot of runtime checks and so on. To enter
Note that not all words displayed here are actual commands on the controller itself. The terminal provides com-
mands itself, they start with a # (hash mark).
To locate the files, the utility checks the current work directory or, if set, the directories from the environment
variable AMFORTH_LIB. Be careful when using a directory with many files, the startup may take a long time
due to the directory tree scanning.
mt@ayla:~/amforth$ grep AMFORTH ~/.profile
AMFORTH=~/amforth
AMFORTH_LIB=$AMFORTH/lib:$AMFORTH/examples
export AMFORTH_LIB
mt@ayla:~/amforth$
Amforth is written in assembly language. Writing assembly words usually requires a rebuild of the hex files
and flashing them to the controller. Lubos Pekny developed an assembler that runs within amforth and does not
require a change of the amforth sources. Its syntax is a mixture of the standard Atmel assembly and forth. The
mnemonics are close to Atmel’s. The forth influence leads to a postfix notation and that the words that do the
actual code generation end with a comma.
Start
To use it, load the file lib/assembler.frt and its dependencies into a running amforth. The assembler uses
word lists to organize itself. The assembler supports all common mnemonics regardless of the controller type.
The assembler words are in a seperate word list. To activate it, the following sequence is typically used:
forth only also assembler
This resets the word list order and adds the assembler word list. After successfully compiling the assembler word,
the word list can be removed with previous.
Simple Example
The example uses the assembler for words that could easily be written in plain forth. Nevertheless an implemen-
tation in assembler is done. The code itself it taken from a posting on Roboforum.RU
$2F constant tccr1a
$2E constant tccr1b
\ stop timer1
\ : t1> 0 TCCR1 c! ;
code t1>
tccr1b R2 out,
78 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
end-code
The new words can be used just like a ordinary forth words.
\ stop timer1 & zero counter
: <t1> t1> 0 dup TCNT1H c! TCNT1L c! ;
\ show t1 counter
: .t1
TCNT1L c@ TCNT1H c@ 8 lshift + dup
." (0x" .x ." )" bl emit u. ." us"
AmForth has a version number, that can be read with an environment query:
> s" version" environment? drop .
50 ok
> s" version" environment search-wordlist drop .
50 ok
>
In addition to this information (esp for those who use the newest revision from the source repository) the built
timestamp maybe useful as well. To get it, AmForth needs to be compiled with the file words/built.asm
included. Calling it prints the date and time the hexfile was generated in the current terminal.
> built
Nov 22 2012 23:12:94 ok
>
The assembly code uses some avr asm specific macros, the string length information is hardcoded.
; ( -- ) System
; R( -- )
; prints the date and time the hex file was generated
VE_BUILT:
.dw $ff05
.db "built",0
.dw VE_HEAD
.set VE_HEAD = VE_BUILT
XT_BUILT:
.dw DO_COLON
PFA_BUILT:
.dw XT_DOSLITERAL
.dw 11
If you are using the subversion sandbox from the sourceforge repository, the following solution from Enoch
provides the subversion revision number.
His solutions extends the Makefile to generate a small forth snippet that contains the information as a string.
AMFORTH := ../amforth/trunk
CORE := $(AMFORTH)/core
DEVICE := $(CORE)/devices/$(MCU)
Running make creates the file svnversion.frt in the current directory that contains the output of the
svnversion -n command. Uploading this file creates the forth command _svnversion_ that prints it in the
terminal.
\ #include svnversion.frt
: myturnkey
\ snip
applturnkey
space svnversion
;
’ myturnkey is turnkey
\ The result:
\ ~~~~~~~~~~~
Adding the name of the current GIT branch is slightly more complex. The first step is creating a template file as
appl/words/git-info.tmpl This file will be transformed into an assembly file with some search-replace
actions during this copy.
; ( -- ) System
; R( -- )
; GIT Info
VE_GITINFO:
.dw $ff08
.db "git-info"
.dw VE_HEAD
.set VE_HEAD = VE_GITINFO
XT_GITINFO:
80 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
.dw DO_COLON
PFA_GITINFO:
.dw XT_DOSLITERAL
.dw @BRLEN@
.db "@BRNAME@"
.dw XT_ITYPE
.dw XT_EXIT
The next step is to add the file words/git-info.asm to the list of included files (e.g. dict_appl.inc).
The final step is to add a rule to the build tool. In this example, ant is used, so edit the build.xml file in the
project application directory as follows:
<!-- change existing rules -->
<target name="uno.hex"
depends="git-info"
description="Hexfiles for ...."/>
<target name="git-info">
<git-branch output="branch" />
<length property="length" string="${branch}"/>
<copy tofile="words/git-info.asm" file="words/git-info.tmpl" overwrite="true">
<filterset>
<filter token="BRLEN" value="${length}"/>
<filter token="BRNAME" value="${branch}"/>
</filterset>
</copy>
</target>
With these settings, a new command is available git-info. It prints the current branch name in the terminal:
> git-info
master ok
>
4.4.3 Coroutines
Coroutines are a computer science building block. From a users perspective they form a way to let code in different
words communicate with each other. Thus coroutines can be seen as a simple way to multitask.
The key command is co:
: co r> r> swap >r >r ;
Producer/Consumer
A producer generates data which a consumer deals with. The example simply generates sequence of numbers
which are printed to the terminal. The sequence ends when a value of 10 is reached.
: producer ( n -- n’ n’ ) begin 1+ dup co again ;
: consumer
0 producer
begin dup . 10 < while co repeat
r> drop drop ;
The producer is quite simple. It is an endless loop that increases the TOS element, duplicates it and calls the
partner. It creates a potentially endless stream of increasing numbers on the stack. For every new number, the
other process (the consumer) is called via co to ... consume this number.
> consumer
0 1 2 3 4 5 6 7 8 9 10 ok
>
The consumer has a little more to do. It is responsible to initially call the producer and to clean up after finishing.
Ceavats
wont work. For such code, the co command needs to go deeper into the return stack.
For the same reason calls to CO inside DO-loops wont work. This is due to the loop parameters on the return
stack.
4.4.4 Ctrl-C
To interrupt a running system at any time and reset it to the prompt a keyboard command ctrl-c is often used.
AmForth can honour such a keystroke as well. To achieve it, a small code change needs to be applied and a new
hex file pair has to be flashed to the controller.
The code change affects the interrupt usart handler (drivers/usart-rx-isr.asm). Here add the 4 lines
5-8:
1 lds xh, USART_DATA
2 ; optional: check for certain character(s) (e.g. CTRL-C)
3 ; and trigger a soft interrupt instead of storing the
4 ; charater into the input queue.
5 cpi xh, 3
6 brne usart_rx_store
7 jmp 0
8 usart_rx_store:
9 lds xl, usart_rx_in
With this change, whenever the keyboard sends the ascii code 3 (for ctrl-c) it is catched immediately and a soft
reset is made. it requires that the WANT_ISR_RX option is set to 1.
Customization takes place when you create the hex files. It requires to edit files and re-generate them using the
assembler.
82 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
All customization is done in the application master file. A good starting point is template/template.asm.
If you change any other file, good luck. You can look for these options in the code however.
Every application is unique. Thus you need to create your own amforth specific to your intended environment.
There is no generic image that works everywhere.
First make a copy of the appl/template directory (myapp in this example).
mt@ayla:~/amforth/appl$ cp -r template myapp
Next edit the template.asm in the mayapp directory. You may want to rename the file later. There are only a
few lines that need your attention.
; include the amforth device definition file. These
; files include the *def.inc from atmel internally.
.include "device.asm"
This line is tricky. It uses the generated include file but does not specify the controller type itself. The magic is in
the list of INCLUDE directory that is defined in the Makefile. Alternatively change the line to
.include "devices/atmega1280/device.asm"
please use the same directory name from the pd2amforth run above. The downside of using the controller-
dependent directory name instead of some makefile variables is that you have to keep the definition of the controller
type in sync in <em>two</em> files. The makefile always need the information for the programmer.
The next essential information is the frequency your controller uses. It is necessary (at least) to calculate the
proper usart settings and to get the right delay in the forth word ms.
; amforth needs two essential parameters
; cpu clock in hertz, 1MHz is factory default
.equ F_CPU = 16000000
The last setting is the command terminal for the prompt. There are a few predefined settings. Unfortunately Atmel
has changed the wording over time. In most cases make sure that the number in the _0 reflects the number in the
RXEN0 definitions and the final 0 in the UCSZ00. Elder controllers do not have a number suffix, just delete it
(atmega32 may serve as an example for it).
; initial baud rate of terminal
.include "drivers/usart_0.asm"
.equ BAUD = 9600
.equ USART_B_VALUE = (1<<TXEN0) | (1<<RXEN0) | (1<<RXCIE0)
.equ USART_C_VALUE = (3<<UCSZ00)
The next file to edit is the Makefile (or the build.xml if you want to use the ant utility). First set the right
controller type:
# the MCU should be identical to the device
# setting in template.asm, it set
MCU=atmega1280
The last change is the placement of the avrasm2.exe and the Appnotes2 directory.
# directories
DIR_ATMEL=../../Atmel
To flash the controller, the program avrdude is used. Depending on your programmer, define the BURNER
variable as well:
# programmers / flags
USB=-c avr911 -P /dev/ttyUSB3
PP=-c stk200 -P /dev/parport0
All other settings can be kept for now. Just run make and look for errors.
WANT - Options
WANT Options are used to select certain features. There is always a default value in place (0).
The files core/devices/$MCU/device.asm contain among other things a complete list of WANT Options
that can be used to include device specific Names into the dictionary.
.set WANT_AD_CONVERTER = 0
.set WANT_ANALOG_COMPARATOR = 0
.set WANT_BOOT_LOAD = 0
.set WANT_CPU = 0
....
Changing these options to 1 includes the matching sections from device.inc into the generated dictionary.
The same effect could be achieved by selectively send the device.frt file sections.
Another such option is the WANT_IGNORECASE option. If it is set to 1, the amforth dictionary lookup routine is
extended to handle upper and lower case words the same. This makes foo and FOO the same. This is a dictionary
wide setting, valid for both pre-defined and self-defined words.
The 3rd group switches the USART terminal communication between interrupt and poll based routines:
.set WANT_ISR_TX = 0
.set WANT_ISR_RX = 1
Settings the value of 1 select the interrupt based routines, otherwise the poll driven routines are used. It is recom-
mended to leave the options as they are set.
See also:
Redirect IO
A debugger is a tool to check data at runtime. For amforth there is no single tool for that purpose. There are a
Tracer and a Profiler available. They modify the code generation to achieve their goals. The debugshell presented
here is called at explicit breakpoints to stop the execution of the current word and gives an independent command
prompt to execute arbitrary commands.
This debugshell core can be modified and expanded in many ways. One example is the Watcher Utility for memory
access.
Core
Technically it is an isolated command shell activated at any time. With this debugger you can place the command
?? anywhere in your code and you’ll get the debug> prompt whenever execution reaches it.
84 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
Extensions
The first extension is to have an on-off feature of the debugger. This can be achieved by an global flag or using
deferred words:
0 value debug?
\ re-defines the ?? command and uses the old one
\ internally
: ?? debug? if ?? then ;
assigning a non-zero value to debug? (true to debug?) will activate the debug prompt. Note that the debug
flag is stored in EEPROM und the settings survive a reset.
Another on-off implementation uses the deferred word technique.
Edefer breakpoint
’ ?? is breakpoint
\ ’ noop is breakpoint
Here you use the command breakpoint in your code instead of the basic ?? command.
: foo bar breakpoint baz ;
Note that the deferred vector is stored in EEPROM and the settings survive a reset.
The third extension uses interrupts. Since amforth executes them as ordinary forth code it is possible to assign any
interrupt source to the ?? command (0 is an example interrupt number)
> ’ ?? 0 int!
> 0 int-trap
When you use an external interrupt via a simple key you get the debug prompt whenever you press it. If you
configure and enable the external interrupt of course. Note that in this case the debug prompt is executed in the
interrupt mode of the controller, you have to use the polling implementation of the usart receive module.
Stack Dumps
Stack dumps can be generated with the command .s. The standard does not specify, how the output has to for-
matted. The built-in command is for core development. This means that numbers are printed as unsigned (hex
is highly recommended) and the TOS is on the left hand side. This makes it easy to get the most important
information easily and the numbers are quickly found in memory dumps and the assembler LST and MAP files.
The output looks like:
> -1 -2 -3 .s
65533 65534 65535 ok
> hex .s
FFFD FFFE FFFF ok
>
Most other forth’s and the various books use another stack dump format. It uses signed numbers and places the
TOS on the right side. This can be achieved with the following definition, kindly provided by Enoch on the mailing
list:
Other stack dumps are as follows. They are kindly provided by Erich:
\ variations on dot-s
\ dot-s, one way, signed output:
: ds sp@ sp0 1 cells - do i @ . -2 +loop ;
Memory Dumps
Atmegas have three different memory address spaces. Each region has its own dump utility
dump Standard Memory. Every Address unit has 8 bits.
> $180 $20 dump
0180 36 30 31 33 33 02 87 75 F4 6D 74 26 8F 63 A3 CD 601CD..u.mt&.c..
0190 44 AB FC D7 3D DA D7 16 59 EB 3F AF 76 F2 27 3F D...=...Y.?.v.’?
ok
edump EEPROM. Similar to RAM, every address unit has 8 bits, but since it used on a cell (16 bits) basis, the
display uses this number width:
> 0 $30 edump
0000 - FFFF 0EA3 0121 0052 0CC7 3B65 0019 0B2B ....!.R...e;..+.
0010 - 0014 0014 0E66 0001 0014 FFFF FFFF FFFF ....f...........
0020 - FFFF FFFF FFFF FFFF 0000 0000 085F 080F ............_...
ok
idump Flash. Unlike the other memories, flash has 16 bits per address unit:
> $dc0 $20 idump
0DC0 - 3830 0DC5 38D0 3837 002E 381A FF05 322E 08...878...8...2
0DC8 - 6568 0078 0DAB 3800 3B23 02D5 02DD 02DD hex....8#;......
0DD0 - 02FF 0430 381A FF05 342E 6568 0078 0DC6 ..0..8...4hex...
0DD8 - 3800 3B23 02D5 02DD 02DD 02DD 02DD 02FF .8#;............
ok
86 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
4.4.8 Exceptions
Exceptions are a way to commicate situations that cannot easily handled. An exception is a number. And only
a number. The process to send an exception is called throw. The communication process follows the call stack
upwardly. At any level it can be catched. Catching an exception means to handle it. It is possible to re-throw an
unhandled exception. The standard amforth system has an outermost exception catcher. It handles all exceptions
by printing their number and returning to the command prompt.
Exceptions are thread local. It is up to the user to catch all exceptions that may occure, since threads do not have
an outermost exception catcher. An unhandled exception freezes the system.
The Forth standard specifies a number of exceptions already. Amforth provides a Subset
The general way to catch an exception is to call a word by it’s execution token with catch. catch is much like
execute except that it is capable to handle exceptions:
: foo -2883 throw ;
: bar ....
[’] foo catch
?dup if ( -- e )
\ ... handle exception or
throw \ re-throw it, leaving bar
then
\ only executed if no exception occured or one got handled
...
;
User supplied exception codes should be in the range -65000 .. -4096. To garantuee uniquness, an exception
number generator should be used. It can be as simple as
-4096 Evalue exception
: exception ( -- n ) exception dup 1- to exception ;
4.4.9 Extended VM
Basic Usage
Both registers A and B act the same way. They are not used inside any standard AmForth code and are not thread
local. Since they use CPU registers, they work faster than variables or other memory based data.
To store data into a register, the command >a is used. Getting back the data is done with a>. Unline the similiar
looking >r, repeated calls to >a overwrite the register contents.
> a> .
6183 ok
> 17 >a
ok
> a> .
17 ok
>
Pointer Voodoo
The registers can work as address registers. The command a@ reads the RAM location, the A register points to.
By using a@+ the data is read and the register is incremented by 1 cell (2 bytes). Similiar the a@-: the data is read
and the register is decremented by 1 cell.
> : dump swap >a 2/ 0 do a@+ . loop ;
ok
> source dump
7320 756F 6372 2065 7564 706D ok
>
To store data, the commands a!, a!+ and a!- can be used. They store the Top-Of-Stack Element to RAM where
the A register points to and modify it afterwards (if applicable).
The words na@ and na! give access to the memory location n bytes relative to the current value of the A register.
The content of the A register is not changed.
> : dump swap >a 2/ 0 do i cells na@ . loop ;
ok
> source dump
7320 756F 6372 2065 7564 706D ok
>
Portable Version
The registers are an extension of the underlying forth VM. There is no official reference implementation available.
To experiment with them, the following code may be useful.
1 cells constant cell
variable reg:a
: >a reg:a ! ;
: a> reg:a @ ;
: a@ a> @ ;
: a! a> ! ;
: na@ a> + @ ;
: na! a> + ! ;
\ post-increment fetch/store
: a@+ cell reg:a +! a@ ;
: a!+ cell reg:a +! a! ;
: a@- cell negate reg:a +! a@ ;
: a!- cell negate reg:a +! a! ;
\ alternativly
\ pre-increment fetch/store
: a@+ a@ cell reg:a +! ;
: a!+ a! cell reg:a +! ;
88 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
During development and testing it is often desirable to start over again and forget everything. Traditional forth’s
have the word FORGET. Amforth uses another, more modern approach: marker.
marker needs planning. Before use, include the file:‘dict_compiler2.inc into your list of include packages to
generate the hex files. Next upload the file lib/ans94/core-ext/marker.frt. If you encouter errors that
the word set-current is not defined, you forgot to include the file dict_compiler2.inc.
Now the command marker creates a named snapshot of the current memory state to that you can return to any time
afterwards. This includes all definitions and wordlists defined after the snapshot is taken. They get completely
deleted and the occupied memories (flash, ram, eeprom) are available again.
> marker empty
ok
> : foo ." foo" ;
ok
> foo
foo ok
> empty
ok
> foo
foo ?? -13 3
> empty
empty ?? -13 5
>
Note that the snapshot itself is gone as well. If you want it again, just re-create it.
They work usually fine. Furthermore they are based upon standard techniques.
Another solution for forward declarations uses a Just-In-Time (JIT) approach. With it a forward declaration
resolves itself when called without further user (or programmer) interaction:
1 > forward: foo
2 > : bar foo ;
3 > bar
4 found only forward declaration.
5 > : foo ." hey" ;
6 > bar
7 hey
8 >
Line 1 declares foo to be defined later. This foo must not be called directly! The next line defines a word bar
that uses foo. Note that foo is not yet a code definition. The word bar can be safely executed however. When the
program execution of bar arrives at foo, the JIT module starts. This module first gets the name of the forwardly
defined word (foo) and looks it up in the dictionary If find-name gets an XT for foo it is checked whether it is
the XT of the forward: declaration or another one. If it is the XT of the forward: declaration, execution is
aborted with an error message.
If an XT is found, that fulfils the requirements, two things happen: First the call to foo in the callee (bar) is
changed from the original one (that goes to the forward declaration) to the new one that is found by find-name.
Plus the XT is executed itself. As a result, a repeated call to bar will not call the JIT runtime checks again but
hands over directly to the new foo.
foo can be redefined again. Any already resolved references will remain, still not resolved references will resolve
to the new definition:
> forward: foo
> : bar foo ;
> : baz foo ;
> bar
found only forward declaration.
> : foo ." I’m number 1" ;
> bar
I’m number 1 ok
> : foo ." I’m number 2" ;
> baz
I’m number 2 ok
> bar
I’m number 1 ok
> baz
I’m number 2 ok
>
The implementation uses internal data structure knowhow. The word forward: creates words that performs the
above discussed runtime behaviour when called inside another definition. It is assumed that they are only called
within a colon definition.
: forward:
dp create ,
does>
dup 1- swap @i here iplace here count ( copy to temporary ram)
find-name if \ unless some wordlist voodoo ...
swap over = abort" found only forward declaration."
dup r@ 1- !i execute
else
\ can only happen if search wordlist has been changed
true abort" unresolved forward declaration"
then
;
Late Binding
A similiar definition to forward: can be used to implement late binding. In this case a forward reference will
not get permanently resolved but looks up the dictionary every time it get called.
: execute-late:
dp create ,
does>
dup 1- swap @i here iplace here count ( copy to temporary ram)
find-name if \ unless some wordlist voodoo...
swap over = abort" found only forward declaration."
execute
else
90 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
This has a huge runtime penalty since on every invocation a dictionary lookup will be made. An option would be
the use of search-wordlist command instead of find-name if a proper (short) word list exists.
> execute-late: foo
> : bar foo ;
> bar
found only forward declaration.
> : foo ." I’m number 1" ;
> bar
I’m number 1 ok
> : foo ." I’m number 2" ;
> bar
I’m number 2 ok
>
There is a lot of C code out there. And there is no easy way to use it in AmForth. This recipe gives some hints for
porting C code. A lot of more examples can be found at Rosetta Code.
AmForth provides the same register names as C. All addresses are memory mapped. Many registers are split into
bitgroups, that got names as well. In C these names are usually bitnumbers, AmForth uses the bitmaps as specified
in the Atmel resource files.
Single bits are straight forward:
C:
TIMSK0 |= (1<<OCIE0); /* set the bit */
TIMSK0 &= ~(1<<OCIE0); /* clear the bit */
AmForth:
\ set the bit
: or! dup c@ rot or swap c! ;
OCIE0 TIMSK0 or!
Control Structures
The control structures are basically all the same. The differences are subtle and usually small. Conditional
Execution
C:
if(flag) { foo(); } else { bar(); }
AmForth:
flag if foo else bar then
Counted Loops
C:
for(i=0;i<10;i++) {
foo();
}
AmForth:
10 0 do foo loop
If the loop increment is not 1, Forth uses the word +loop instead of loop:
C:
for(i=0;i<10;i+2) {
foo();
}
AmForth:
10 0 do foo 2 +loop
4.4.13 Profiler
Sometimes it is useful to watch a word working. The Tracer gives many informations, which may be confusing
or un-usable at all. The number of calls of a given word can be more instructive. This is the time for the profiler
utility.
variable profiling?
: profile:on -1 profiling? ! ;
: profile:off 0 profiling? ! ;
After loading it into the controller, every colon word gets a counter (1 cell) which is incremented every time the
word is called. Since this cell can be used like any variable, it can be reset any time as well.
> : foo 1 ;
ok
> profiler:on
ok
> ’ foo xt>prf @ .
0 ok
> foo
ok
> ’ foo xt>prf @ .
1 ok
> 0 ’ foo xt>prf !
ok
>
4.4.14 Quotations
Quotations are a programming technique to embed code inside of code. These embedded code snippets have an
execution token but no name token.
Quotations use two new commands [: and ;]:
92 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
Quotations are not (yet) standardized by the forth2012 committee, but they seem to do so in the near future.
The amforth implementation has no dependencies and is used to implement some value-variants: Double Cell
RAM Value and inside the Serial Peripheral Inteface SPI
RAM is probably the scarcest resource of an atmega. To make the best of it, some additional words may be helpful.
cvariable
cvariable acts like variable but does not allocate a cell (2 bytes) but only 1 byte of RAM. Access to it is limited
to c@ and c!. To indicate the size, one may want to use the Hungarian Notation.
There are a few possible implementations. One uses carnal knowledge of the inner workings, the other one relies
on the fact that 1 cell is 2 bytes RAM in amforth.
: cvariable
here constant 1 allot ; \ carnal knowledge
\ troublesome
answer @ . \ undetermined
4242 answer ! \ destroys other data
See also:
Defining and using Arrays and the cvalue section in Values
4.4.16 Recognizer
The Forth text interpreter is able to work with numbers and command words. Its main purpose is to transform
the text representation into a format closer to the system level and deal with them. Numbers are converted to
their binary form for the data stack, command words are found in the dictionary and are further dealt with their
execution tokens (and header flags).
In standard Forth there is no easy way to add new data types to the text interpreter and to associate actions with
them for the different interpreter states. For example there are no native string literals. They are mimicked by
using a command word (s").
A recognizer fills this gap. It consists of two major parts: A word which does the parsing and converting. And a
group of three methods for dealing with the data, the parsing word produces. These methods are used in interpret
and compile state, and to postpone the data in colon definitions.
Amforth has recognizers for dealing with numbers and words from the dictionary built-in. To create and manage
more recognizers, the words get/set-recognizer are used. They work similar to the get/set-order
for word lists.
The word recognizer: takes three execution tokens and defines the method table. The word to parse the
input stream takes a string as input and leaves either the method table r:fail (and no further data) or some data
together with the method table defined with recognizer:. The interpreter takes care of the rest. It is possible
to modify >in inside the parsing word if the data contains whitespace. Debugging such words can be tricky
however.
String Literals
A string is delimited by two " symbols. The first one starts the string and the next one is the end of it. Everything
in between is the string content. A string is denoted by its start address and its length. When compiling, the string
needs to copied to the dictionary together with a runtime action.
Since a string can contain whitespace, the parsing word needs to deal with >in. The string address and length is
valid for the lifetime of the SOURCE buffer only, a refill will change the content.
’ noop
’ sliteral
:noname type -48 throw ;
recognizer: r:string
’ rec:string place-rec
The first line is simply the method table definition. The first two methods are already defined in amforth so nothing
special here. The third method is called when the data is beeing postponed. For now, a string cannot be postponed,
which would essentially lead to a string copy from the defining word to the new one. Instead an exception -48 is
thrown.
The rec:string definition is more complex. The first line
over c@ [char] " <> if 2drop r:fail exit then
is the check whether the current word start with a " character. If it does not, the two arguments are dropped and
the special method table r:fail is returned.
If the first character is a " the main task is to find the delimiting next ". Since the >in needs to be set to the
location of this character as well, we use the word parse which does this work for us.
negate 1+ >in +! drop \ reset parse area to SOURCE
This line re-adjusts the parsing area to the beginning of the word inside SOURCE. The code
[char] " parse \ get trailing delimiter
scans the whole input for the delimiting " and returns it. Finally some address cosmetics has to be done to include
the very first character as well.
Finally the r:string method table is returned together with the string itself. The last command adds the string
recognizer to the list of the recognizers the interpreter uses and activates it this way. Now we can enter strings as
native data without the s" command.
> "foo" type
foo ok
> " foo" type
foo ok
> " foo" type
foo ok
94 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
4.4.17 Tracer
Sometimes it is useful to watch a word working. A simple trace utility that prints the name of the word and the
stack content at the beginning helps to get important information.
\ flag to dynamically turn trace output
\ on and off
variable tracing?
: trace:on -1 tracing? ! ;
: trace:off 0 tracing? ! ;
: tracer tracing? @ if cr itype cr .s else drop drop then ;
After loading these few lines into the controller, every word being defined afterwards prints it’s name and the stack
content at runtime.
> : foo 1 ;
ok
> : bar 2 foo ;
ok
> : baz 3 bar ;
ok
> trace:on
ok
> baz
baz
bar
0 2221 3
foo
0 2219 2
1 2221 3
ok
> .s
0 2217 1
1 2219 2
2 2221 3
ok
> trace:off
ok
> baz
ok
>
It requires amforth version 4.7 and up. (sliteral is missing in earlier versions).
This tracer is based on posts from Emma Ledwidge and Gerry in the usenet group comp.lang.forth in January
2012.
You may want to upgrade AmForth if you encounter a bug that is fixed in a later revision or want to make use of
a certain new feature. In this recipe I assume that you use the standard filesystem layout.
The first step is to unpack the new release archive into a new directory. Do not try to overwrite an existing
installation. The 2nd step is a full copy of the Atmel/ directory from your existing installation into the new tree.
This copy has to include the Appnotes*/ directories and the avrasm32.exe file from Atmel. These files are
verbatim copies from an Atmel AVR Studio installation and are not included into the AmForth distribution (guess
why).
The next step is to make sure, that the template sample application can be compiled without problems. If you
encounter any error, fix it first. If everything went well, you can copy your application directory from the old
tree into the new directory tree and carefully re-apply all changes that the template application has got since you
started your own application. The major source for information is the change log on the AmForth Webpage and
the Source Code Repository.
This recipe gives some hints how to protect AmForth from being (partially) destroyed and to be able to recover
from accidents without re-flashing the system.
Flash protection
The first line should be a flash protection. It prevents the !i to write to places where it should not. This can be done
by creating a new word, that does some bounds checking and does the final write command only if everything is
ok.
\ write protect everything up to this command
: save-!i [ dp 12 + ] literal over <
if (!i-nrww) else -20 throw then ;
’ save-!i is !i
After these few lines, all flash up to this definition is now write-protected. All forbidden access will generate an
exception. The offset added makes sure that our new command protects itself as well.
The code in the NRWW section (file:dict_appl_core.inc) is already write protected, the controller itself makes
sure of that. A write attempt to this locations does not generate an exception, it will be ignored silently.
EEPROM protection
Protect the EEPROM is more difficult. AmForth rewrites a few cells during normal development, which makes a
simple write protection as described for the flash rather useless. Furthermore AmForth uses the EEPROM content
at very early stages in the boot process. Any safety action needs thus be hard-coded in warm and it will need a
trigger to start the EEPROM recovery. This could be a check for some data or a hardware based information.
As long as the command prompt works, the data that got saved by a marker definition is sufficient to reset to a
working system.
4.4.20 Values
The standard VALUE gives access to memory content like a variable does. The difference between these two is
that a value gives a actual data whereas a variable leaves the address of the data on the stack. The place, where a
value stores the data is usually not known. There is only one way to change it: use of TO.
96 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
This resembles the intended usage pattern for EEPROM: Write seldom, read often.
The forth standard defines a few value types: 2VALUE for double cell data, FVALUE for floating point numbers
and the single cell sized VALUE itself. They all use the same TO command to change their content. This requires
a non-trivial implementation to achieve it. Amforth uses a simple data structure for each value in the dictionary
(flash). The first element contains the address of the actual data. This first field is followed by 2 execution tokens
(XT) for the read and write operations. This makes the runtime operations fairly easy. The read operation (the 2nd
element in the data structure) is called with the address of the 1st element. It is expected that the read operation
leaves the data on the data stack. Similiar the write operation. The TO command simply executes the write
execution token (the 3rd element).
This generic approach allows not only single cell data in EEPROM but any data everwhere. The following exam-
ples illustrate this with an implementation of a value that stores a single byte in RAM and a cached version of the
standard EEPROM value. They have in common that calling their names give the data and applying TO to them
stores new data.
cvalue
Cvalues store a single byte in RAM. The first element in the value data structure in the dictionary is the address of
the RAM byte. The defining word allocates it. Like any other RAM based data its content is not preserved over
resets and restarts.
\ two helper functions, not called directly
: c@v @i c@ ;
: c!v @i c! ;
: cvalue ( n "name" -- )
(value) \ create a new wordlist entry
here , \ the address of the RAM memory
[’] c@v , \ method for the read operation
[’] c!v , \ method for the write (TO) operation
here c! \ initialize the RAM content
1 allot \ formally allocate the RAM byte
;
After its definition the new size restricted value is used like any other value. To read it, simply call its name. To
write to it, use the TO command. As a bonus, all operations are save against overflows:
> $dead to answer
ok
cached Value
A cached value is a value that stores the data in EEPROM but tolerates heavy write access by using a RAM cell as
a cache. This RAM cell gets all write operations. The eeprom is not written until an explicit flush is performed.
At startup the cache needs to be warmed, this is not done automatically.
\ 2 is a magic number
: @cache 2 + @i @ ;
: !cache 2 + @i ! ;
: cache-value
(value) \ create the vocabulary entry
dup ehere dup , dup cell+ to ehere !e \ allocate an EEPROM cell.
[’] @cache , \ XT for the read method
[’] !cache , \ XT for the write methon
here 2 ( 1 cell ) allot dup , ! \ allocate a RAM cell and initialize it
;
The following example session creates a cached value and demonstrates the content of the two memory’s during
normal execution.
> ehere \ keep the eeprom address for later direct access
ok
> 42 cache-value c-dp
ok
> 17 to c-dp
ok
> c-dp . dup @e .
\ RAM and EEPROM contents are different!
17 42 ok
> ’ c-dp flush-cache
ok
> c-dp . dup @e .
17 17 ok
>
Note that there is a difference in programming style between the load/store and the addiional warm/flush opera-
tions. The latter use a code sequence like
’ value method
Its fairly simple to achieve the TO schema for the other commands as well, but since this requires a parsing word
(which is state smart too) the forth gurus consider this suboptimal. A second argument against may be the growing
acceptance of the OO notation object method with object beeing kind of an address.
: flush
’ state @ if
postpone literal postpone flush-cache
else
flush-cache
98 Chapter 4. Cookbook
AmForth Documentation, Release 5.3
then
; immediate
: 2rvalue ( d -- )
(value)
here ,
[: @i 2@ ;] ,
[: @i 2! ;] ,
here 2! 4 allot
;
This value stores a double cell information in RAM. The read and write methods are embedded as quotations.
Wordlists are the building block of the dictionary. A wordlist is a single linked list of entries. Entries are compiled
colon words, assembly words or data structures created with create. The link chain ends when the next pointer is
zero. A wordlist grows usually upward in the flash memory, while the links point downwards.
The anchor of a wordlist is the stored in an EEPROM cell, which address is the wordlist identifier.
Walking a wordlist requires the following steps
1. get the WID (e.g. environment)
2. read the starting address from the EEPROM (line 2) It the name field address of the first word.
3. start the loop until zero is reached (lines 4+5)
4. keep the vital iterator data (line 6)
5. do some work with the entry, consuming the NFA-copy from the previous line (line 7)
6. go to the next entry (line 8)
7. repeat the loop body
The implementation of the word show-wordlist may illustrate this:
1 : show-wordlist ( wid -- )
2 @e
3 begin
4 ?dup
5 while
6 dup
7 icount $ff and itype space
8 nfa>lfa @i
9 repeat
10 ;
The sequence $ff and masks the entry flags (e.g. immediate) and extracts the actual string length for use with the
following itype.
Way easier is using the traverse-wordlist available since amforth version 5.2. With it, the above changes
to
: show-wordlist ( wid -- )
[’] show-word swap traverse-wordlist
;
4.4.22 Watcher
A Watcher is a tool that monitors the access to a memory region. If a predefined memory location is accessed
(read, written to or both) something is done in addition. In its simplest case, a message is printed.
The next few code lines use a single watch address. Any access to it is trapped and calls the Debug Shell.
\ core routines
variable watch-addr
defer watch-action
\ redefine memory access words
: ! dup watch-addr @ = if watch-action then ! ;
: c@ dup watch-addr @ = if watch-action then c@ ;
: c! dup watch-addr @ = if watch-action then c! ;
\ this one is the last one
: @ dup watch-addr @ = if watch-action then @ ;
\ simply use the debugshell
’ ?? is watch-action
\ possible modifications
\ use an address range
\ use a list of addresses (address ranges)
After loading these lines, any word that uses memory access words will be watched for access to a particular
address. If it is accessed, the debug shell will come up for further work.
FIVE
REFERENCE CARD
5.1 General
AmForth is a 16bit ITC forth. It is almost compatible with the forth standards from 1994 and 200x. It runs on the
bare metal controller with no further dependencies. The interpreter operates on whitespace delimited words. The
compiler is a single pass compiler that writes directly to the flash based dictionary.
There are three distinct address spaces for flash, eeprom and RAM. Flash is addressed word wise (16 bits per
address unit), RAM and EEPROM is accessed byte wise (8bits per address unit). The standard return stack has 40
cells, the data stack is limited by the available RAM size.
Numbers can be prefixed by $ to indicate hexadecimal, % for binary and # for decimal numbers. A trailing dot is
used for double cell numbers.
Words not found here are not part of the compileable core system. Their forth sources are in the /lib directory,
usually named after the word name: e.g. 2dup is defined in a file named 2dup.frt.
5.2 Arithmetics
• 1- ( n1 – n2 ) optimized decrement
• 1+ ( n1|u1 – n2|u2 ) optimized increment
• 2/ ( n1 – n2 ) arithmetic shift right
• 2* ( n1 – n2 ) arithmetic shift left, filling with zero
• abs ( n1 – u1 ) get the absolute value
• >< ( n1 – n2 ) exchange the bytes of the TOS
• cell+ ( a-addr1 – a-addr2 ) add the size of an address-unit to a-addr1
• cells ( n1 – n2 ) n2 is the size in address units of n1 cells
• d2/ ( d1 – d2 ) shift a double cell value right
• d2* ( d1 – d2 ) shift a double cell left
• dabs ( d – ud ) double cell absolute value
• dinvert ( d1 – d2) invert all bits in the double cell value
• d- ( d1 d2 – d3 ) subtract d2 from d1
• dnegate ( d1 – d2 ) double cell negation
• d+ ( d1 d2 – d3) add 2 double cell values
• invert ( n1 – n2) 1-complement of TOS
• log2 ( n1 – n2 ) logarithm to base 2 or highest set bitnumber
• lshift ( n1 n2 – n3) logically shift n1 left n2 times
101
AmForth Documentation, Release 5.3
5.3 Character IO
5.4 Compare
5.5 Compiler
5.6 Conversion
5.7 Core
• bounds ( n1 n2 – n2 n1 n2 ) Copy the first (top) stack item below the second stack item.
• tuck ( n1 n2 – n2 n1 n2 ) Copy the first (top) stack item below the second stack item.
5.8 Dictionary
5.9 Environment
5.10 Exceptions
5.11 Extended VM
• na! ( n offs – ) Write memory pointed to by register A plus offset (Extended VM)
• nb@ ( n1 – n2 ) Read memory pointed to by register B plus offset (Extended VM)
• nb! ( n offs – ) Write memory pointed to by register B plus offset (Extended VM)
• >a ( n – ) Write to A register (Extended VM)
• >b ( n – ) Write to B register (Extended VM)
5.12 Interpreter
• fail:d ( – addr ) Method to print a double cell number and throw exception
• fail:i ( – addr ) Method to print a number and throw exception
• get-recognizer ( – recn .. rec0 n) Get the current recognizer list
• postpone ( c” ” – ) postpone
• rec:find ( addr len – f ) recognizer searching the dictionary
• rec:intnum ( addr len – f ) recognizer for integer numbers
• r:fail ( – addr ) there is no parser for this recognizer, this is the default and failsafe part
• r:find ( addr len – f ) Methode table for find recognizer
• r:find ( addr len – f ) Methode table for find recognizer
• r:find ( addr len – f ) Methode table for find recognizer
• r:find ( addr len – f ) Methode table for find recognizer
• r:intdnum ( – addr ) Method table for double cell integers
• r:intnum ( – addr ) Method table for single cell integers
• set-recognizer ( recn .. rec0 n – ) replace the recognizer list
5.13 Interrupt
5.14 Logic
5.15 MCU
5.16 Memory
5.17 Multitasking
• cas ( new old addr – f ) Atomic Compare and Swap: store new at addr and set f to true if contents of addr is
equal to old.
• pause ( – ) Fetch pause vector and execute it. may make a context/task switch
5.18 Numeric IO
• base ( – a-addr ) location of the cell containing the number conversion radix
• bin ( – ) set base for number conversion to 2
• d. ( d – ) singed PNO with double cell numbers
• d.r ( d w – ) singed PNO with double cell numbers, right aligned in width w
• decimal ( – ) set base for numeric conversion to 10
• digit? ( c – (number|) tries to convert a character to a number, set flag accordingly
• . ( n – ) singed PNO with single cell numbers
• .r ( n w – ) singed PNO with single cell numbers, right aligned in width w
• hex ( – ) set base for number conversion to 16
• hld ( – addr ) pointer to current write position in the Pictured Numeric Output buffer
• hold ( c – ) prepend character to pictured numeric output buffer
• <# ( – ) initialize the pictured numeric output conversion process
• number (addr len – [n|d size] f) convert a string at addr to a number
• # ( d1 – d2 ) pictured numeric output: convert one digit
• #> ( d1 – addr count ) Pictured Numeric Output: convert PNO buffer into an string
• #s ( d – 0 ) pictured numeric output: convert all digits until 0 (zero) is reached
• sign ( n – ) place a - in HLD if n is negative
• >number ( ud1 c-addr1 u1 – ud2 c-addr2 u2 ) convert a string to a number c-addr2/u2 is the unconverted
string
• ud. ( ud – ) unsigned PNO with double cell numbers
• ud.r ( ud w – ) unsigned PNO with double cell numbers, right aligned in width w
• u. ( u – ) unsigned PNO with single cell numbers
• u.r ( u w – ) unsigned PNO with single cells numbers, right aligned in width w
• u0.r ( ud n – ) Print n digits, fill in preceeding zeros if needed
5.20 Stack
5.21 String
5.22 System
• accept ( addr +n1 – +n2 ) receive a string of at most n1 characters at addr until n2 characters are reveived
or cr/lf detected.
• allot ( n – ) allocate or release memory in RAM
• built ( – ) prints the date and time the hex file was generated
• cold ( – ) start up amforth.
• defer@ ( xt1 – xt2 ) returns the XT associated with the given XT
• defer! ( xt1 xt2 – ) stores xt1 as the xt to be executed when xt2 is called
• (defer) ( i*x – j*x ) runtime of defer
• (value) ( – n ) runtime of value
• Edefer@ ( xt1 – xt2 ) does the real defer@ for eeprom defers
• Edefer! ( xt1 xt2 – ) does the real defer! for eeprom defers
• execute ( xt – ) execute XT
• f_cpu ( – d ) put the cpu frequency in Hz on stack
• f_cpu ( – d ) put the cpu frequency in Hz on stack
5.25 Systemm
• Rdefer@ ( xt1 – xt2 ) does the real defer@ for ram defers
5.26 Time
5.27 Tools
• [char] ( – c ) (C: “<space>name” – ) skip leading space delimites, place the first character of the word on
the stack
• [compile] ( – c ) (C: “<space>name” – ) skip leading space delimites, place the first COMPILEacter of the
word on the stack
• char ( “<spaces>name” – c ) copy the first character of the next word onto the stack
• .s ( – ) stack dump
• ee>ram ( e-addr r-addr len – ) copy len cells from eeprom to ram
• find-name ( addr len – 0 | xt -1 | xt 1 ) search wordlists for the name from string addr/len
• icompare ( r-addr r-len f-addr f-len – f) compares string in RAM with string in flash
• icount ( addr – addr+1 n ) get count information out of a counted string in flash
• init-user ( – ) setup the default user area from eeprom
• itype ( addr n – ) reads string from flash and prints it
• noop ( – ) do nothing
• n@e ( ee-addr n – itemn .. item0) Get an array from EEPROM
• n!e ( recn .. rec0 n ee-addr – ) Write a list to EEPROM
• ?stack ( – ) check stack underflow, throw exception -4
• show-wordlist ( wid – ) prints the name of the words in a wordlist
• show-wordlist ( wid – ) prints the name of the words in a wordlist
• to ( n <name> – ) store the TOS to the named value (eeprom cell)
• unused ( – n ) Amount of available RAM (incl. PAD)
• ver ( – ) print the version string
• word ( c – addr ) skip leading delimiter character and parse SOURCE until the next delimiter. copy the
word to HERE
• words ( – ) prints a list of all (visible) words in the dictionary
5.29 unclassified
SIX
HISTORY
• core: Automatic scoping of words. A system hook can be used to use a different wordlist than CURRENT
to place a new word in. Thanks to Enoch for the idea and the code.
• lib: very flexible CRC8 checksum generator and checker. Thanks to Enoch.
• recipes: Interrupt Critical Section, Unbreakable AmForth, Efficient Bit Manipulation, Dump Utilities, Ctrl-
C Thanks to Enoch and the others on the mailling list for code and inspiration.
• core: -int does no longer leave the SREG register. It only turns off the global interrupt flag. Thanks to
Enoch.
• lib: major 1-wire enhancements: CRC checks and a better naming convention for all words. Thanks to
Erich for help and substantial contributions.
• appl: added the Arduino Leonardo. avrdude needs a small patch to write properly the eeprom on the
Atmega32U4.
• core: New popcnt (n – m) counts the Hamming Weight of the given number.
• core: renamed baud to ubrr.
• core: nfa>lfa is a factor in a number of words. It generates the link field address from a given name field
address.
• doc: Farewell docbook XML, welcome reST. All documentation will be written in reStructured Text.
• lib: Simple Quotations. Their typical use case is
: foo ... [: bar baz ;] ... ;
which is equivalent to
:noname bar baz ; Constant#temp#
: foo ... #temp# ...;
• lib: Access to Dallas 1-Wire Devices. Based on code and ideas by Bradford J. Rodriguez for the 4 C4th
project.
• lib: many Arduino ports have more than one purpose. The forth200x Synonym gives them useful alias
names.</a>
• Arduino: Added definitions for all ports based upon Digital Ports.
• recipes: There are now more than 30 Cookbook in the cookbook: many debug tools, loop with timeout,
porting from C, and interrupts to mention some of them.
113
AmForth Documentation, Release 5.3
• core: autogenerate sleep depending on register availability. sleep on an Atmega32 is very different from an
Atmega328p. The parameters for calling it at the forth level are the same however. The include list for the
assembler is expanded with core/<device>/ to find the right sleep.asm file.
• core: rudimentary error checks in the compiler: There has to be branch destination on the stack. If there is
nothing, a stack underflow exception gets thrown.
> : ?do i . loop ;
?? -4 14
> : t2 ?do i . loop ;
ok
>
• core: Number sign may follow the number base prefix as specified in Forth200x Number Prefix. Added the
character # as prefix for decimal as well.
• core: fixed a regression in toupper caused by making within standards compliant. Thanks to Arthur for the
fix. [compile] fixed as well.
• core: initialisation of the USER area is now done in WARM. please check your TURNKEY to remove the
call to it. Thanks to Erich for pointing to.
• core: regenerated the devices files with the part description files from studio v6. added bitnames to the forth
and python modules (later to be used with the shell).
• tools: completly new shell program with cool upload features from Keith: amforth-shell It has command
completion, full command history, automatic controller identification with all register names and much
more. Updated the Use of the amforth-shell.py utility for this task
• lib: re-arranged source files, improved timer modules.
• lib: case did not work at all. Thanks to Jan for telling.
• core: the new variable latest has the XT of the currently being defined colon word.
• core: unused should tell the free amount of memory in the area here points to: RAM. Thanks to Carsten
for the hint.
• core: introducing an environment query for basic controller information: memory sizes, max dictionary
address: mcu-info. The structure itself is not yet finalized. See at the end of a core/<device>/device.asm
file for details.
• tools: The upload utilities were unable to process absolute filenames (those beginning with a /) Thanks to
Carsten for the fix.
• core: environment? can now be used in colon definitions. changed into loadable forth source instead of
compile-time assembly.
• core: itype now sends proper (e.g. single byte) characters to emit.
• core: type is made more robust against emit errors.
• lib: macro and a Defining and using Macros recipe for using them.
• Profiler to count the number of calls.
• lib: evaluate for both RAM and Flash based strings.
• core: words shows the first entry in the search order list as specified by DPANS94.
• lib: new word m*/ (d1 n1 n2 – d2), uses a triple cell intermediate for d1*n1.
• lib: new words bm-set, bm-clear and bm-toggle that efficiently change bits in RAM byte addresses. e.g.
:command:‘ %0010 here bm-toggle‘ changes bit 2 in the RAM location at here.
• lib: renamed spirw to c!@spi, new word !@spi exchanges two bytes via SPI. Follows remotly the memory
access word naming conventions.
• arduino: re-arranged word placing to maximize usable flash (at least on a duemilanove device, the bigger
variants like the sanguino and mega* still have room for improvement). The target mega is now called
mega128.
• lib: lib/buffer.frt implements buffer:.
• doc: improved refcard. Thanks to Erich for input and patches.
• core: changed API of the Recognizer to the final addr/len pairs. Do not use counted strings any longer!
• core: new words find-name and parse-name follow Forth 200x and operate on the current input buffer,
word is no longer used internally. Lots of internal code simplifications.
• core: (create) throws exception -16 if no name is given.
• core: exception -42 is really -4 (stack underflow).
• core: digit? again. Stack effect now compatible to gforth: ( char – n true | false). Current setting of base
is now taken internally.
• core: fixed a regression for i! which made marker useless (among other oddities). Thanks to Marcin for
the fix
• core: currently defined colon words are invisible until the final ;.
• applications: Leon contributed a IEEE754 floating point library in plain forth, Pito translated some basic
words into assembly for speed.
• tools: amforth-upload.py optionally loads a device specific module and replaces register definitions with
their values prior to sent the code to the controller. The device modules are auto-generated from the part
description files.
• core: ANS94 mention that HERE points to the data (RAM) region. Re-introduced DP as the dictionary
(Flash) pointer. HEAP is gone. Migrate old HEAP to HERE and old HERE to DP.
• core: save and clear the initial value of the MCU Status Register at address 10.
• tools: pd2amforth is now capable to generate the device definition files. It is no longer necessary to edit
them manually.
• core: finally separated the terminal IO settings from the device definition files.
• core: optionally set WANT_SPI (or any other IO Module) to include the register definion names at build
time.
• core: massivly restructured the devices/ filesystem entry. Change your application files to include
device.asm instead of the device name. Set the include directory to the proper subdirectory under
core/devices as well.
• core: dynamically calculate the free space. Do not use all of it however, the data stack may grow.
s" /pad" environment?
• core: Simplified the Pictured Numeric Output words. They now use the memory area below pad (which is
100 bytes above HEAP) as the buffer region.
• appl: added the arduino board with some example codes. Currently with the Mega (Atmega1280), Duemi-
lanove (Atmega328) and Sanguino (Atmega644p) controller types.
• core: fix for icompare to make it work with all addr/len strings. Bug found and fixed by Michael and Adolf.
• core: re-implemented the i! in (mostly) assembly language to ease integration into bootloaders.
• core: factor the three prompts into compile time changable words.
• appl: the dict_minimum.inc und dict_core.inc files need to be included within the application
defition files.
• core: pad is no longer used by amforth itself.
• core: reorder internal code in interpret to get rid of 0= calls.
• core: new word environment. It provides the environment wordlist identifier, thus make it possible to create
own environment queries as standard words.
• core: new word d=.
• core: amforth runs partially on an atmega2561 and atxmega’s, there is still no working flash store word (i!)
therefore only the interpreter is available yet.
• core: moved the usart init values to appl section.
• core: added a poll-only receive word, selectable at compile time. Disable the rx interrupt to use it.
• core: re-structure the usart code, added a non-interrupt based transmit word (TX), selectable at compile
time.
• lib: added xt>nfa that goes from the XT to the name field address.
• core: bugfix recurse.
• core: restructured EEPROM, never depend on fixed addresses for system values.
• core: added a dict_wl.inc file with most of the non-core wordlist commands.
• core: renamed the words for the serial terminal to be more generic since they can deal with any serial port,
not only the first one.
• lib: dropped forget since it cannot work with multiple wordlists, fixed marker.
• core: changed again digit? stack effect (and fixed a little bug).
• core: number honors a leading &, $ or % sign to temporarily switch to DECIMAL, HEX or BIN base resp.
Thanks to Michael Kalus for factoring the code.
• core: heap, here and edp are now VALUEs. dp is gone (use here)
• lib: more VT100 sequences.
• core: The TIB location and size are accessible with the VALUEs TIB and TIBSIZE.
• core: fixed TIBSIZE default configuration.
• lib: created math.frt, contains among others the standard words sm/rem, fm/mod.
• Alexander Guy fixed a bug in u*/mod.
• Bernard Mentink adapted Julian Noble’s Finite State Machine code.
• applications: Lubos Pekny designed a smart computer with a 4line character LCD and a PS/2 keyboard.
Details are in the Application Repository, a video is available as well.
• core: Lubos Pekny found that -jtag sometimes used the wrong mcu register.
• core: Bernard Mentink wrote a Atmega128 device file, Thanks alot.
• core: Atmega88 & Atmega168 work too.
• core: Fixed regression for atmega128.
• core: Moved serial interface words to application dictionary (not every amforth installation may have a
serial terminal).
• library: Updated assembler from Lubos Pekny.
• examples: sieve benchmark, optimized for 1K RAM.
• core: new defining words code and end-code. code starts a new dictionary header with the XT set to the
data field. The 2nd one appends the jmp NEXT call into the dictionary.
• core: removed the pre-assembled case / endcase words. Added them as forth library.
• core: new words -jtag (turns off JTAG at runtime) and -wdt (turns off watch dog timer at runtime. They
need to be implemented as primitives due to timing requirements.
• core: quit: Keep base when handling an exception.
• library: TWI/I2C EEPROM Support.
• Bug: hex 8000 . froze the controller. Now it prints -8000. Thanks to Lubos for the hint.
• Moved init of base from quit to cold. turnkey be used to change it permanently. Thanks to Lubos for the
hint.
• nice looking dumper words for RAM/EEPROM/FLASH, dropped idump.asm.
• Extended Upload utility (tools/amforth-upload.py) from piix: include files using following syn-
tax:
\ demo file
#include ans94/marker.frt
marker empty
• changed stack effect for # to ansi (from single cell value to double cell). Double cell values do not work
(yet).
• introduced deferred words instead of tick-variables. Works for EEPROM based vectors (turnkey), RAM
based (pause) and User based (emit etc) vectors.
• new words: wdr (Watchdog reset), d> and d< (double cell compare).
• interrupt handling redesigned. Now every interrupt (except those for usart0) can be used. intcounter is
gone. New words are int@, int! and #int.
• double and mixed cell arithmetics.‘
• bugfix: proper initialization of data stack pointer. Thanks to Maciej Witkowiak.
• new word: f_cpu sets a double cell value with the cpu clock rate.
• hld is now at pad to save RAM.
• pad did return some compile-time stochastic value‘
• lots of internal changes.
• optional dictionary: d-, d+, s>d and d>s.
• changed: itype and (new) icount refactored by Michael Kalus. These words now have similiar stack effects
as there RAM counterparts.
• changed: . now operates on signed values.
• new word: u/mod is basically the former /mod.
• new word: u. to display unsigned values.
• fixed bug in /mod for values less -FF (hex).
• create left the address of the XT insted of the PFA. Fixed.
• deleted word: idump. It is now in the file blocks/misc.frt.
• new word: :noname ( – xt) creates headerless entry in the dictionary.
• new word: cold as main entry point. It executes the turnkey action. abort & co do not trigger the turnkey
action.
• pad is now in the unsed (according to heap) ram. That may help word to store longer strings.
• new word: unused ( – n) gives the number of unused flash cells in the dictionary.
• /mod (and / and mod) now honor signed numbers, division is symmetric.
• new word: abort”
• quit now aborts on every catched exception.
• quit no longer prints anything, ver is now a turnkey action.
• new optional dictionary, included at compiletime. Contains now case & Co and some d- words for
double cell arithmetics.
• emit, key and key? are now vectored via user based variables.
• forget frees most of the flash space too
• internal go back for i! to previous code
• Code for Atmega8 was broken due to nrww flash overflow (found by Milan Horkel)
• Bugfix: backspace key in accept now stops at beginning of line (found by Milan Horkel)
• interrupts in high level forth colon words (INT0 and INT1 for now).
• new word: noop a colon word for doing nothing.
• number respects minus sign
• changed turnkey into ‘turnkey. The "turn-off" value is now 0 (zero)
• new words: pause and ‘pause. pause will execute the XT stored in ‘pause (a RAM cell) when non zero
• handler (used by cactch and throw) is a USER variable.
• New website
• Atmega16 works fine
• Bugfixing, true flag always 0xffff
• Compiler works
• Many new wrds