Where I implement Forth on the Feather RP2040 using Mecrisp-Stellaris Forth.
- Mecrisp Forth
- Mecrisp Forth Glossary
- Mecrisp Forth Dictionary
- Mecrisp Downloads
- Mecrisp-Stellaris Unofficial User Documentation
- Mecrisp General Discussion
- Pi Pico Memory Map for Forth
- Run Forth on Pico Video
- Raspberry Pi Pico W
- Adafruit Feather RP2040
- USB to TTL Serial Cable - Debug / Console Cable for Raspberry Pi
- Lithium Ion Polymer Battery - 3.7v 1200mAh
How to Begin
(Follow directions based on your board)
Its worth one’s time to watch the video referenced above provided by 0033mer. They’ve got a good approach and if you are unsure of what to do, the video will help.
That said, I had did the following:
1. Download the latest version
I downloaded 2.6.5. You will need to expand the compressed folder by double-clicking (or right-clicking).
2. Install Forth on board
The installation is identical to Micropython, press and hold Boot/SEL, press/release Reset then release Boot/SEL. This will set the board up as a USB device. Now drag /mecrisp-stellaris-2.6.5/rp2040-ra/mecrisp-stellaris-pico-with-tools.uf2 to RPI-RP2 (it will show up as a USB drive) and the board will reset itself.
Its best to add a reset button to the breadboard by connecting a pushbutton between GND and pin 30 on the Pico.
The installation is identical to Micropython, press and hold Boot/SEL, press/release Reset (the button you just added) then release Boot/SEL. This will set the board up as a USB device. Now drag /mecrisp-stellaris-2.6.5/rp2040-ra/mecrisp-stellaris-pico-with-tools.uf2 to RPI-RP2 (it will show up as a USB drive) and the board will reset itself.
3. Attach serial and power
As you will be using the UART and not the USB to communicate with Forth, you may unplug the USB C cable. You may also leave it connected, if you wish to power the board via the USB C cable, however see note below. I opted to power the board using a battery.
From Adafruit here is a description of the cable: “Inside the big USB plug is a USB<->Serial conversion chip and at the end of the 36” cable are four wire - red power, black ground, white RX into USB port, and green TX out of the USB port."
* Black wire to GND (Feather Pin 4) * Red wire unconnected (see note below) * Green to RX (Feather Pin 14) * White to TX (Feather Pin 15)
##Note on Powering the Feather## It is not recommended, but technically possible: Connect an external 5V power supply to the USB and GND pins. Not recommended, this may cause unexpected behavior when plugging in the USB port because you will be back-powering the USB port, which could confuse or damage your computer. See Feather Power Management.
I connected a USB cable to the micro-USB connector on the board for power and a serial cable (same as above) to:
* White to TX (Pico Pin 1) * Green to RX (Pico Pin 2) * Black wire to GND (Pico Pin 3) * Red wire **unconnected** (Power will come from USB cable)
Serial Terminal Connection
Start up your favorite serial program and you’ll be good to go. Almost.
As stated in other entries, I really like macOS app, Serial. I attached the cables and Serial immediately showed a serial connection available. I selected the connection, double-clicked and I was connected. It wasn’t a good connection. I had a serious issue with carriage returns in that my Serial window looked like this: It took a considerable of experimentation to determine, I needed to set the following in Serial: Update: After more work, while the image above solves it, however introduces more CRLF issues and causes buffer issues. A good solution is to use the INIT file below. The best solution is to fix the code.
In trying to understand a bit better, I read the directions. In the README, there was a comment about picocom:
# To connect, one can use the following picocom command: picocom -b 115200 /dev/cu.usbserial-ftDEWXAX --imap lfcrlf,crcrlf --omap delbs,crlf*
I would have done well, to follow the instructions as I used picocom and it works perfectly. Well-chastened, I sat down and read the documentation and I ran across this entry “Stair Stepping”. Yep, that is exactly what happened to me. Read it if you still have the issue.
This Mecrisp Glossary page is what you need to study in order to write in Mecrisp-Stellaris Forth. This page has the words and very simple definitions of the words that comprise MSForth. I would also recommend two modern books which also follow the ANS-standard Forth. Both are from Forth, Inc and can be purchased via your favorite book-seller:
- Forth Programmers Handbook, 3rd Edition Conklin and Rather
- Forth Application Techniques, 6th Edition Rather
Forth - the “fun” part
A great explanation as to why this section exists and its something to remember as to the why and how of Forth. Yes, Forth has this specific idiosyncrasy, however, it is specifically this issue which is why Forth is so powerful when writing for microcontrollers. Read Terry’s comments as well on this issue.
Forth is a domain-specific AND author-specific language. This means (as Terry points out on his page regarding Common User Issues), is that different Forths have different words to do the same thing. I call this the “fun” part of Forth. It means every time you begin to use a new Forth, you must examine the dictionary (or words list) and determine two things:
- Does it have the words you frequently use?
- If so, do they mean the same thing?
- If not, what does the same thing (as the word you frequently use)?
A good example
Typically, to list the words in the dictionary, one uses the word “words”. In MSForth, words will print a list of the words, with the address, pointers etc like this: (partial)
words Address: 200000C0 Link: 20000518 Flags: 0000FFFF Code: 200000EA Name: --- Mecrisp-Stellaris RA 2.5.9 --- Address: 20000518 Link: 2000053C Flags: 00000242 Code: 20000524 Name: 2dup Address: 2000053C Link: 2000055C Flags: 00000262 Code: 20000548 Name: 2drop Address: 2000055C Link: 200005A4 Flags: 00000244 Code: 20000568 Name: 2swap Address: 200005A4 Link: 200005CC Flags: 00000262 Code: 200005B0 Name: 2nip Address: 200005CC Link: 200005E8 Flags: 00000044 Code: 200005D8 Name: 2over Address: 200005E8 Link: 2000060C Flags: 00000044 Code: 200005F4 Name: 2tuck Address: 2000060C Link: 20000630 Flags: 00000046 Code: 20000618 Name: 2rot Address: 20000630 Link: 20000654 Flags: 00000046 Code: 2000063C Name: 2-rot Address: 20000654 Link: 2000067C Flags: 00000220 Code: 2000065E Name: 2>r Address: 2000067C Link: 200006A8 Flags: 00000220 Code: 20000686 Name: 2r> Address: 200006A8 Link: 200006D8 Flags: 00000220 Code: 200006B2 Name: 2r@ Address: 200006D8 Link: 20000704 Flags: 00000220 Code: 200006E6 Name: 2rdrop Address: 20000704 Link: 2000071C Flags: 00000042 Code: 2000070E Name: d2/ Address: 2000071C Link: 20000734 Flags: 00000262 Code: 20000726 Name: d2* Address: 20000734 Link: 20000750 Flags: 00000042 Code: 20000740 Name: dshr Address: 20000750 Link: 2000079C Flags: 00000262 Code: 2000075C Name: dshl Address: 2000079C Link: 200007B0 Flags: 00000042 Code: 200007A8 Name: dabs Address: 200007B0 Link: 200007D0 Flags: 00000042 Code: 200007BE Name: dnegate Address: 200007D0 Link: 20000820 Flags: 00000264 Code: 200007DA Name: d-
And if you want simply a list of the words, then use the word “list”. As in this: (partial, and note that it is all one single line.)
list --- Mecrisp-Stellaris RA 2.5.9 --- 2dup 2drop 2swap 2nip 2over 2tuck 2rot 2-rot 2>r 2r> 2r@ 2rdrop d2/ d2* dshr dshl dabs dnegate d- d+ s>d um* m* ud* udm* */ */mod u*/ u*/mod um/mod m/mod ud/mod d/mod d/ f* f/ 2! 2@ du< du> d< d> d0< d0= d<> d= sp@ sp! rp@ rp! dup drop ?dup swap nip over tuck rot -rot pick depth rdepth >r r> r@ rdrop rpick roll -roll and bic or xor * clz ror rol arshift rshift lshift 0= 0<> 0< true false >= <= < > u>= u<= u< u> <> = min max umax umin move fill @ ! +! h@ h! h+! c@ c! c+! bis! bic! xor! bit@ hbis! hbic! hxor! hbit@ cbis! cbic! cxor! cbit@ cflash! hflash! rom-code rom-data connect-flash enter-xip exit-xip erase-range program-range flush-cache image>spi-offset erase# save# load# save new restart + - 1- 1+ 2- 2+ cell+ negate not shr shl 2* cells 2/ abs u/mod /mod mod / even base binary decimal hex hook-emit hook-key hook-emit? hook-key? hook-pause emit key emit? key? pause serial-emit serial-key serial-emit? serial-key? cexpect accept tib >in current-source setsource source query compare cr bl space spaces [char] char ( \ ." c" s" count ctype type hex. h.s u.s .s words unused registerliteral, call, literal, create does> <builds ['] ' postpone inline, ret, exit recurse state ] [ : ; e
With this in mind, let’s begin to enter words which define our first program. When a word which we want to use isn’t defined, we’ll begin to look for an equivalent.
Know How to Erase
One more thing, we know we’re going to make mistakes, so we need to know how to erase. Typically in Forth, you set a marker to identify where you began a series of definitions. This allows you to say “marker” and it will delete everything up to the word defined by marker.
And as you begin to development Forth in incremental steps, you set a marker where each set of words becomes well-defined, debugged and ready for use. MSForth, doesn’t have the word marker. It has the “forgetram” word to erase everything in RAM. Which means it will erase all of what you have written, so it is a blunt instrument. For now, it works.
While its common to blink the LED as your first program, I will take a different approach. I’m going to use an example in an older Forth book “Forth: A Text and Reference” by Mahlon G. Kelly and Nicholas Spies. From page 12 in the book, its a graphing program.
Bar Chart Program
The program is a simple one that creates bar charts on your terminal given a set of Y data points (X is assumed to be consecutive numbers starting at 1). The x axis is on the left side of your monitor with the beginning value at the bottom, which means you will be viewing it sideways. A good use of it would be to plot values from a ADC port indicating the analog values read.
Enter each line separately by copy and paste. This ensures that each line is compiled correctly (shown with an ok.) before you enter the next line. For now, its go slow, to go fast. :)
: .X ." X" ; ( The character to be printed. ) : BAR ( n -- ) 0 DO .X LOOP CR ; ( Plot a bar of X ) : LIMITBAR ( n -- ) DUP 85 > IF DROP 85 THEN BAR ; ( BAR max is 85 ) : GRAPH ( n1 n2 n3 ... -- ) CR DEPTH 0 DO LIMITBAR LOOP ; ( Show graph ) 10 20 30 40 50 40 30 20 10 GRAPH 10 20 30 40 150 40 130 20 100 GRAPH
: .X ." X" ; ( The character to be printed. ) ok. : BAR ( n -- ) 0 DO .X LOOP CR ; ( Plot a bar of X ) ok. : LIMITBAR ( n -- ) DUP 85 > IF DROP 85 THEN BAR ; ( BAR max is 85 ) ok. : GRAPH ( n1 n2 n3 ... -- ) CR DEPTH 0 DO LIMITBAR LOOP ; ( Show graph ) ok. 10 20 30 40 50 40 30 20 10 GRAPH XXXXXXXXXX XXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXX XXXXXXXXXX ok.
Check your work
If you use the word “list”, it will show the words you added as the latest words in the dictionary:
list GRAPH LIMITBAR BAR .X --- Mecrisp-Stellaris RA 2.5.9 --- 2dup 2drop 2swap 2nip 2over 2tuck 2rot 2-rot 2>r 2r> 2r@ 2rdrop d2/ d2* dshr dshl dabs dnegate d- d+ s>d um* m* ud* udm* */ */mod u*/ u*/mod um/mod m/mod ud/mod d/mod d/ f* f/ 2! 2@ du< du> d< d> d0< d0= d<> d= sp@ sp! rp@ rp! dup drop ?dup swap nip over tuck rot -rot pick depth rdepth >r r> r@ rdrop rpick roll -roll and bic or xor * clz ror rol arshift rshift lshift 0= 0<> 0< true false >= <= < > u>= ...
Use the word “forgetram” to wipe out what you’ve done and re-enter it using copy and paste of the entire paragraph (from .X through GRAPH definitions). Did it work? If not, Terry recommends adding an end-of-line delay of 200ms. He has numerous hints in how to solve serial issues, try searching his website using the word serial. (Why do it twice? The second time you do it, will the most common method of entering data. The first time, line-by-line, was to reduce the possibilities of errors.)
Additional Notes Regarding Input
I continued to have issues with stair-stepping. Its fine when its fixed, however, when it pops back up, its maddening! I’ve determined the best (partial) solution is this file:
\ words required for stair-stepping \ both need to stay in flash, however don't...yet. compiletoflash : emit-crlf ( c -- ) dup 10 = if 13 serial-emit then serial-emit ; : INIT ['] emit-crlf hook-emit ! ; compiletoram
when I figure out why the words are not staying in flash, I’ll advise, as that will be the best solution. The intent of INIT is that if it is in flash, Forth will execute it first on restart. At this point in time, reset continues to erase it from flash…
Update on INIT
Apparently, one must save to Dictionary 0, for it to remain in flash after a reset. So the process is:
- Load the file containing the INIT word (and all other desired words) [see simple example below]
- Execute a “0 save#” to save to dictionary 0
- Power-cycle, reset, press reset and the words in dictionary 0 will be reloaded
\ words required for stair-stepping \ both need to stay in flash compiletoflash : emit-crlf ( c -- ) dup 10 = if 13 serial-emit then serial-emit ; : INIT ['] emit-crlf hook-emit ! ; \ graph program from pg 12 Forth:Text and Reference : .X ." X" ; ( The character to be printed. ) : BAR ( n -- ) 0 DO .X LOOP CR ; ( Plot a bar of X ) : LIMITBAR ( n -- ) DUP 85 > IF DROP 85 THEN BAR ; ( BAR max is 85 ) : GRAPH ( n1 n2 n3 ... -- ) CR DEPTH 0 DO LIMITBAR LOOP ; ( Show graph ) 0 save# compiletoram