In the past AmForth's download has included precompiled hex files for the UNO and other Arduino boards. I would imagine they still include them. All you need is an ISP programmer to install the hex file, and you are good to go.
Lots of interesting stuff in Amforth, but it looks like the Atmel processors aren't the best suited to doing Forth. It works, but the memory map is rather ugly and the RAM is a bit limited to implement the old style disk words.
Given that the Arduino ecosystem supports a number of different processor architectures, I wonder if there is another UNO compatible board that would be a better choice. Are there any Atmel processors that provide enough pins with the same functionality that is available on the UNO board, plus would allow something like 64K of external RAM? Being able to use existing shields is important.
This is all a hobby for me and I'm really getting the itch to build something. It would be fun to start with an existing design that has the appropriate copyright and design a board. I've been wanting to dig into EagleCAD anyway.
At this point, I suppose all of this conversation should go into another thread, or maybe into a different forum. Feel free to make suggestions.
Look at '1284P boards. 16k SRAM, 128K flash, 32 IO.
I designed my Bobuino pinout to very closely follow the Uno: D0,D1 for serial, D10-11-12-13, for SPI, D18-19 for SCL, etc. This board I offer follows the Uno footprint, with the extra 10 pins broken out as well.
http://www.crossroadsfencing.com/BobuinoRev17/
A teensy 3.1 ought to make a great Forth system. Single address space, 64k of RAM (plus lots of flash), and probably really fast.
But... this is where the "support" issue comes in. With AMForth, there's a team of people (well, at least one person, plus a significant number of users) who is actively working on making sure all of the "annoying" bits of the AVR architecture are adequately dealt with. For the more capable processors, what you usually get is someone saying "I built C-forth for this chip and it seems to work. Here's the makefile diff", and that's it. No "support", no "community."
AmForth supports the atmega2560 so you should be able to use an Arduino Mega. It works just fine on a 1284p also.
Interestingly, there does appear to be some Forth work for the Teensy 3.1, but I haven't looked at it myself. Maybe a flash in the pan.
https://search.yahoo.com/search?p=teensy+forth
I did use Laboratory Microsystems Forth quite a bit on the original IBM 5150 PC back in the mid-80s, and did find it to be fun [once I gave up on the "official" Forth philosophy from Leo Brodie and the guys, and stopped using the stupid stack operations other than dup and over, and went to using ! and @ for most operations instead - that made it fun]. Hmmm, might be fun to look at it for the Teensy.
Oh man! FORTH is the best language ever! When I wanted to program the (then new) Macintosh computer, you got a manual the size of the Chicago phone book, and a couple floppy disks containing an assembly language system (not even really an IDE.) That seemed awkward, so I used that assembler to write Pocket Forth modeled after Brodie's Forth from his two books, and from a DDJ article about subroutine threaded code on the 68000 chip. The rest is ... well, the history of Forth is kind of sad. ANSI Forth and Forth 83 appear to have been changed by making the language more C like and more obscure at the same time. After that, most Forth projects got turned into C projects, and the magic went away.
Arduino seems like it would like Forth, and I just got 328eForth from this guy. It's free, but if you want the complete package, it's $25.
Wow, I'm assuming that "2165: stm32eforth720, eForth for Discovery32, $25" on the Disks page successfully supports the F4 chip, but the description on this page seems a little equivocal.
http://www.offete.com/stm32eForth.html
That would be sublimely awesome as the F4 is a monster of a chip. ST also has a Discovery F429 board that I've been eyeballing.
Here is a wild attempt of writing a Forth Virtual Machine for the Arduino.
120 primitives/instructions in the Virtual Machine. 3.5 Kbyte without the kernel dictionary strings (pure vm). Byte token thread with a built-in instruction level trace (optional). Multi-tasking with context switch back to the Arduino sketch on forth level yield, delay and halt.
Cheers!
kowalski:
Here is a wild attempt of writing a Forth Virtual Machine for the Arduino.GitHub - mikaelpatel/Arduino-FVM: Byte Token Threaded Forth Virtual Machine (FVM) for Arduino
120 primitives/instructions in the Virtual Machine. 3.5 Kbyte without the kernel dictionary strings (pure vm). Byte token thread with a built-in instruction level trace (optional). Multi-tasking with context switch back to the Arduino sketch on forth level yield, delay and halt.
Cheers!
Cool, thanks for posting! I've put playing with this in my queue.
The latest development of the Arduino Forth Virtual Machine (Arduino-FVM) includes a Token Compiler and a traditional Forth outer interpreter that allows definitions in data and program memory.
Token Compiler, Arduino-FVM/Compiler.ino at master · mikaelpatel/Arduino-FVM · GitHub
Forth Interpreter, Arduino-FVM/Forth.ino at master · mikaelpatel/Arduino-FVM · GitHub
The byte token threaded inner interpreter introduces tail call optimization. The built-in instruction level trace will show how this optimization works at run-time.
Cheers!
Sweet! I just downloaded it, and it's in my queue. Thanks for all the work you have (obviously) put into this project.
I am posting here as it seems to be the most recent Thread about Forth.
I dabbled with Forth years ago. My recollection is that it performs very well yet is pretty much an interpreted language. How would it compare with the same task programmed in C++?
I've been wondering if I could have the interpreter in the Arduino and the code for my user program on an SD Card. If so, then it should be possible to send another "text" file which it loads onto the SD Card and thens switches over to use the new file as its program?
Does that make sense? is it possible with any of the existing implementations?
Would reading the program from an SD Card slow things too much to be useful?
...R
I dabbled with Forth years ago. My recollection is that it performs very well yet is pretty much an interpreted language. How would it compare with the same task programmed in C++?
Forth is usually "compiled" into a very efficient token-like sort-of interpreted "threaded code."
I don't think it really compares very well to a true compiled language like C, but it does pretty good, and in some cases might be more space-efficient.
I've been wondering if I could have the interpreter in the Arduino and the code for my user program on an SD Card. If so, then it should be possible to send another "text" file which it loads onto the SD Card and thens switches over to use the new file as its program?
Probably not so much. On AVRs, Forth programs are usually burnt into flash (though they don't NEED to be, if they're small enough, I guess.) Most of the Forths I've looked at have assumed lots of RAM...
Forth does have a pre-filesystem mass storage concept, which is mostly (IMO) pretty bogus, but it does mean that it could support EEPROM or Serial flash chips without having to implement a filesystem.
Thanks. On that basis I suspect it would be more trouble than it might be worth.
And it did occur to me after I had posted Reply #32 that if I could "send" a program file and have it used as "the" program there would probably be a major problem if the new program crashed.
...R
I think "bitlash" supports running programs from external storage, including "chaining" to a new program.
It's a BASIC-like interpreter...
I have always assumed that Bitlash would slow things down a great deal - far more than Forth.
...R
Well you can always do what I did for an expression evaluator called "functionfactory" for ImageFX on the Amiga:
Use lex, Yacc and a grammar to create a parser for your input and "compile" it. I don't think you have the memory for that, though.
[ Would to hitch-hike and not start a new Forth thread. ]
SEE CODE
Passing the address of a function to a simulated stack ..how to do so?
Hi, I'm trying to suss out (understand) how to pass the address of a function to a simulated stack structure, from inside a standard Arduino IDE based block of code
(I don't even know what language this is -- C or C++ perhaps -- whatever is considered standard, and compiles in the distribution IDE for Arduino).
The application consists of a host PC talking to an Arduino M0 Pro Atmel SAMD21G18A utilizing ARM Cortex M0+ technology via USB tty (the development board presents a new device, /dev/ttyACM0 to the Linux host PC, as soon as the USB cable connects the two machines together).
The two machines chat via something similar to RS-232c, only it is imposed over USB somehow. minicom (or GTKTerm for that matter) is used on the Linux host PC to talk to the microcontroller development board (the Arduino M0 Pro, currently about USD 40.00 from several vendors).
The application code causes the Arduino board to function similarly to a (rather brain-dead) dial-up BBS (bulletin board system) in that they chat using ASCII characters, as well as ANSI.SYS escape codes (using MS-DOS jargon).
The application is meant to more or less simulate not only a dial-up BBS experience (just barely so) but more to the point, to simulate a working GForth environment (again, only the rudaments, and not with close adherance to the original, by any means).
CODING WORDS INLINE has not proved to be of much difficulty; Charley Shattuck lit the path nicely. What I wanted to do next was to start work on branch-conditionals (as I'll call them). Tests. I want to be able to implement something that, in Forth, might read:
$ cat ~/howtrueistthat.fs
: howtrue
." equals zero - yes, that is true here. It does equal zero."
;
: 0=? ( n -- ) \ equals zero? - test
0= IF howtrue exit then ;
$ gforth ~/howtrueistthat.fs
Gforth 0.7.2, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Type `bye' to exit
5 0=? ok
0 0=? equals zero - yes, that is true here. It does equal zero. ok
bye
$
Now, the trick is, to pass the address of 'howtrue' into the stack, (faked stack) so that it can be conditionally called, depending on how things progress as the user interacts with this primitive 'system'.
I don't know how to
a) learn or ascertain the exact address of 'howtrue' except by disassembly, which doesn't quite help, here; and
b) how to exec code at that address, once found and passed into the (faked) stack.
I can see by the assembler dump that the code addresses exist and seem to beg to be exec'd by calling their actual addresses (in flashROM space, near 0x42e0 or so):
$ /usr/bin/arm-none-eabi-objdump -S -z \
-I/home/proj/Arrduino/public_aa \
/tmp/arduino_build_263967/public_aa.ino.elf
--- excerpt ---
000042e0 <_Z2eqv>:
}
/* is TOS equal to TOS -1? */
NAMED(_eq, "="); // ( n n -- bool ) consumes both arguments, returns boolean
void eq() {
42e0: b510 push {r4, lr}
int a;
int b;
a = pop();
42e2: f7ff ffb3 bl 424c <_Z3popv>
42e6: 1c04 adds r4, r0, #0
b = pop();
42e8: f7ff ffb0 bl 424c <_Z3popv>
if ( a == b ) {
42ec: 4284 cmp r4, r0
42ee: d101 bne.n 42f4 <_Z2eqv+0x14>
true_();
42f0: f7ff ff96 bl 4220 <_Z5true_v>
}
}
42f4: bd10 pop {r4, pc}
000042f6 <_Z3zeqv>:
EDIT: changed ref. address and added sample objdump
(1 of 3 intended, sequential posts)
THE CODE
This isn't Charley's unmodified code -- has local mods.
/* public_aa.ino */
/* Tue Feb 21 20:48:49 UTC 2017 */
/* locally from: interpreter-cortex_m0.ino */
const char versionbuf[] = "VERSION 0.0.01bP ";
/* mcu: Atmel SAMD21G18A
board: Arduino M0 Pro
Technology: ARM Cortex M0+ */
/* derived from: a Forth-like interpreter for the M0
in the Arduino IDE language environment,
by Charley Shattuck */
/* https://github.com/CharleyShattuck/Feather-M0-interpreter
A Forth text interpreter for the Adafruit Feather M0, using the Arduino IDE.
Charley says:
This example code is in the public domain */
/* ***************** code **************************** */
/* Structure of a dictionary entry */
typedef struct {
const char *name;
void (*function)();
} entry;
/* const stores in flashROM: */
const char str[] = "My rather curiously long string, as deposited in flashROM.";
/* simulated stack, with a storage depth of 16 integer values */
const int STKSIZE = 16; // must be a power of 2
const int STKMASK = 15; // STKSIZE less 1
int stack[STKSIZE]; // let's call it a stack as that is how it will be used, sort-of.
int p = 0;
/* TOS is Top Of Stack */
#define TOS stack[p]
/* NAMED creates a string in flash */
#define NAMED(x, y) const char x[]=y
/* Terminal Input Buffer for interpreter */
const byte maxtib = 16;
char tib[maxtib];
/* buffer required for strings read from flash */
char namebuf[maxtib];
byte pos;
/* push n to top of data stack */
void push(int n) {
p = (p + 1)& STKMASK;
TOS = n;
}
/* return top of stack */
int pop() {
int n = TOS;
p = (p - 1)& STKMASK;
return n;
}
/* ************* forth words ************* */
/* discard top of stack */
NAMED(_drop, "drop");
void drop() {
pop();
}
/* recover dropped stack item */
NAMED(_back, "back");
void back() {
for (int i = 1; i < STKSIZE; i++) drop();
}
/* copy top of stack */
NAMED(_dup, "dup");
void dup() {
push(TOS);
}
/* exchange top two stack items */
NAMED(_swap, "swap");
void swap() {
int a;
int b;
a = pop();
b = pop();
push(a);
push(b);
}
/* add top two items */
NAMED(_add, "+");
void add() {
int a = pop();
TOS = a + TOS;
}
/* locally-contributed code follows */
/* multiply top two items */
NAMED(_mult, "*");
void mult() {
int a = pop();
int b = pop();
TOS = a * b;
}
/* invert all bits in top of stack */
NAMED(_invert, "invert");
void invert() {
TOS = ~(TOS);
}
/* negate top of stack */
NAMED(_negate, "negate");
void negate() {
TOS = -(TOS);
}
/* push boolean 'true' (-1) */
NAMED(_true, "true");
void true_() {
push(1);
negate();
}
/* consider the binary representation for the numbers
expressed in decimal, here */
/* logic is inverted, not negated */
/* push boolean 'false' (0) onto the stack */
NAMED(_false, "false");
void false_() {
true_();
invert();
}
/* is TOS equal to TOS -1? */
NAMED(_eq, "="); // ( n n -- bool ) consumes both arguments, returns boolean
void eq() {
int a;
int b;
a = pop();
b = pop();
if ( a == b ) {
true_();
}
}
/* Is TOS equal to zero? */
NAMED(_zeq, "0="); // ( n -- bool ) yes it consumes TOS
void zeq() {
int a = 0;
push(a);
eq();
}
/* need a visible consequence to a boolean decision,
for testing at the serial tty (console) linux knows
as the /dev/ttyACM0 tty device.
So, 'truemsg' and 'falsemsg'.
*/
/* smart mouthed computer */
NAMED(_truemsg, "truemsg");
void truemsg() {
char buffer[10] = "So true. "; Serial.print(buffer);
}
/* and a bit curt or abrupt. */
NAMED(_falsemsg, "falsemsg");
void falsemsg() {
char buffer[15] = "That's false. "; Serial.print(buffer);
}
/* "is not equal to zero */
NAMED(_zneq, "0<>"); // ( bool -- )
void zneq() {
int bool_ = pop();
if (bool_) {
truemsg();
}
else {
falsemsg();
}
}
NAMED(_empty, "empty"); // ( n n n n ... -- ) empty the stack or zero it out, in this case
void empty() {
for (int i = 0; i < 18; i++) push(0);
}
/* end of locally-added code */
/* resume original author's coding: */
/* destructively display top of stack, decimal */
NAMED(_dot, ".");
void dot() {
Serial.print(pop());
Serial.print(" ");
}
/* display whole stack, decimal */
NAMED(_dotS, ".s");
void dotS() {
for (int i = 0; i < STKSIZE; i++) dot();
}
/* dump 16 bytes of RAM in hex with ascii on the side */
void dumpRAM() {
char buffer[5] = "";
char *ram;
int p = pop();
ram = (char*)p;
sprintf(buffer, "%4x", p);
Serial.print(buffer);
Serial.print(" ");
for (int i = 0; i < 16; i++) {
char c = *ram++;
sprintf(buffer, " %2x", (c & 0xff));
Serial.print(buffer);
}
ram = (char*)p;
Serial.print(" ");
for (int i = 0; i < 16; i++) {
buffer[0] = *ram++;
if (buffer[0] > 0x7f || buffer[0] < ' ') buffer[0] = '.';
buffer[1] = '\0';
Serial.print(buffer);
}
push(p + 16);
}
/* dump 256 bytes of RAM */
NAMED(_dumpr, "dump");
void rdumps() {
for (int i = 0; i < 16; i++) {
Serial.println();
dumpRAM();
}
}
/* more local code */
char ascii_char;
/* send one ascii character to the serial port */
NAMED(_emit, "emit");
void emit() {
ascii_char = pop();
Serial.print(ascii_char);
}
NAMED(_2emit_, "2emit");
void _2emit() {
emit();
emit();
}
/* everybody loves a nop */
NAMED(_nopp, "nop");
void nopp() { }
/* End of Forth interpreter words */
/* Now build the dictionary */
/* empty words don't cause an error */
NAMED(_nop, " ");
void nop() { }
/* Forward declaration required here */
NAMED(_words, "words");
void words();
/* How many words are in the dictionary? */
NAMED(_entries_, "entries");
void _entries();
/* table of names and function addresses in flash */
const entry dictionary[] = {
{_nop, nop},
{_words, words},
{_entries_, _entries},
{_drop, drop},
{_dup, dup},
{_back, back},
{_swap, swap},
{_add, add},
{_mult, mult},
{_invert, invert},
{_negate, negate},
{_true, true_},
{_false, false_},
{_eq, eq},
{_zeq, zeq},
{_truemsg, truemsg},
{_falsemsg, falsemsg},
{_zneq, zneq},
{_empty, empty},
{_dotS, dotS},
{_dot, dot},
{_dumpr, rdumps},
{_2emit_, _2emit},
{_emit, emit},
{_nopp, nopp},
};
const int entries = sizeof dictionary / sizeof dictionary[0];
/* Display all words in dictionary */
void words() {
for (int i = entries - 1; i >= 0; i--) {
strcpy(namebuf, dictionary[i].name);
Serial.print(namebuf);
Serial.print(" ");
}
}
/* how many words are there? */
void _entries() {
int a;
a = entries;
Serial.print(a);
Serial.print(" ");
}
/* Find a word in the dictionary, returning its position */
int locate() {
for (int i = entries; i >= 0; i--) {
strcpy(namebuf, dictionary[i].name);
if (!strcmp(tib, namebuf)) return i;
}
return 0;
}
/* Is the word in tib a number? */
int isNumber() {
char *endptr;
strtol(tib, &endptr, 0);
if (endptr == tib) return 0;
if (*endptr != '\0') return 0;
return 1;
}
/* Convert number in tib */
int number() {
char *endptr;
return (int) strtol(tib, &endptr, 0);
}
char ch;
void ok() {
if (ch == '\r') Serial.print("ok\r\n");
}
/* Incrementally read command line from serial port */
byte reading() {
if (!Serial.available()) return 1;
ch = Serial.read();
Serial.print(ch); // char-by-char input, echo
if (ch == '\n') {
Serial.print("\r\n"); // echo
return 1;
}
if (ch == '\r') {
return 0;
}
if (ch == ' ') return 0;
if (pos < maxtib) {
tib[pos++] = ch;
tib[pos] = 0;
}
return 1;
}
/* Block on reading the command line from serial port */
/* then echo each word */ // later: not anymore. We don't do that now.
void readword() {
pos = 0;
tib[0] = 0;
while (reading());
// Serial.print(tib); // we do our own echo now
}
/* Run a word via its name */
void runword() {
int place = locate();
// test for upper bound of 'place'
// this was critical to making Charley's code function without the Serial Monitor e.g. from within minicom or GTKTerm.
if ((place != 0) & (place < (entries - 1))) {
dictionary[place].function();
ok();
return;
}
if (isNumber()) {
push(number());
ok();
return;
}
Serial.println("?");
}
/* Arduino main loop */
void setup() {
Serial.begin(38400);
while (!Serial);
Serial.println ("Forth-like interpreter:\r\n");
Serial.print(versionbuf);
words(); // optional - may comment this out.
Serial.println();
}
void loop() {
readword();
runword();
}
// end.