Mecrisp-Stellaris Forth: An IDE for the RP2040

7 minute read

Where I describe how I develop Forth on my Mac using Sublime Text 3 and Serial, as my Forth IDE.

Summary

  1. Edit Forth code in Sublime Text 4.
  2. Execute ST’s Build command which transfers the code to the board for on-board compiling for Forth, typically a 1-2 second transfer (1500 bytes/sec).
  3. Use the macOS app, Serial for communicating with Forth on the board.

Sources

Install Forth on RP2040

1. Setup

Increase Baud Rate

Make the change in: mecrisp-stellaris-x.y.z/mecrisp-stellaris-source/rp2040-ra/terminal.s lines 165-166 and added the comment below the lines, replacing the comment line 168:

.equ UART0_IBAUD, 8
.equ UART0_FBAUD, 77

@ Baud rate numbers for above -115200 67/52 -460800 16/71 -921600 8/77

The calculations as to how this works is on this page.

Fix the stair-stepping issue

Edit the file by making the change in: mecrisp-stellaris-source/common/datastackandmacros.s and change six lines by adding a \r :

# edit mecrisp-stellaris-source/common/datastackandmacros.s
# Find/Replace
\Meldung\n
\Meldung\r\n
# 6 lines are changed

Recompile

One step command to assemble/link/build UF2 code, very, very nice!

cd mecrisp-stellaris-x.x.x/
./release

Download uf2 file to board and test.

Press & hold Boot/Sel, press & release Reset then release Boot/Sel. Drag the file mecrisp-stellaris-pico-with-tools.uf2 to RPI-RP2.

Communication with Forth

Serial Program

I use macOS and the application, Serial to communicate with the RP2040 board. Once the ’\r’ has been added to the Forth prompt, its important to have the communication program setup as well. In Serial, I need to have the Return Key: LF and Interpret Standalone LF as CRLF checked to ensure everything works as expected.

My setup looks like this:

Serial setup

Serial setup

Large Version to see detail

Editing: Sublime Text 3 (ST3)

I edit my file in ST3, using an old TextMate bundle for Forth. (Update: You may also install a Forth bundle using the Package install command in ST4.) It does a pretty good job of highlighting Forth syntax properly. Once I’ve finished editing, I need to send the text file to the RP2040.

ST3 has the ability to automate a build, whether its for a C++ program or Python. Or in my case, “build” is simply transferring the file to the board and having Forth compile it. I’m using Piotr’s a absurdly simple transfer program, xfr.py and saved it per ST3’s requirements, I saved it in

"~/Library/Application Support/Sublime Text 3/Packages/User/forth.sublime-build":
{
    "cmd": ["/Users/lkoepsel/Documents/python/forth/sxfr.py", "$file"],
    "selector": "source.forth",
}

Uploading via sxfr.py

The stand-alone app which is the basis of the build function in ST.

#!/usr/bin/env python3
# tested with Pico W and dict0.fs and works at 921600
import sys
import serial


ser = serial.Serial(port='/dev/cu.usbserial-0001', baudrate=921600)
for line in open(sys.argv[1], "rt"):
    ser.write(str.encode(line))
    r = ser.readline()
    print(str(r))
    if not r.endswith(b'ok.\n'):
        sys.exit(1)

Add Disconnect/Connect (macOS)

The Serial app has the ability to connect and disconnect using Cmd-D keystrokes. As the serial port may only have 1 application connected at a time, this means its important to disconnect Serial, upload then reconnect Serial to the Pico. I discovered a way to do this in Python using Applescript (macOS only). Here is the new sxfr.py program:

Improved sxfr.py with disconnect/reconnect

#!/usr/bin/env python3
# uploads a file to Mecrisp Forth RP2040
# will disconnect serial port in Serial application prior
# then will reconnect, leaving you in the Serial Application
import serial
import subprocess
from time import sleep
import sys


# Serial Disconnect Connect
# Sends a Cmd-D to Serial to disconnect then reconnect serial port
def Serial_CmdD():
    cmd = """
        activate application "Serial"
        tell application "System Events" to keystroke "d" using command down
     """
    result = subprocess.run(['osascript', '-e', cmd], capture_output=True)
    return result.returncode


# upload a file to Forth, waiting for response on each line
def upload():
    for line in open(sys.argv[1], "rt"):
        ser.write(str.encode(line))
        r = ser.readline()
        print(str(r))
        if not r.endswith(b'ok.\n'):
            print(f"not r.endswith")


Serial_Connected = 0
while(Serial_Connected < 2):
    try:
        ser = serial.Serial(port='/dev/cu.usbserial-0001', baudrate=921600)
        print(f"Uploading to {ser.name}")
        Serial_Connected += 1
        upload()

    except OSError:
        # print(f"Serial Error {e}")
        Serial_Connected += 1
        if Serial_Connected == 1:
            rs = Serial_CmdD()
            # sleep req'd to prevent a race condition with Serial
            sleep(.05)

ser.close()
Serial_CmdD()

Programming in Mecrisp Forth

Every Forth has its idiosyncracies and Mecrisp is no different. In this case, Forth executes out of RAM and programs may be stored in Flash. This might cause some initial confusion, however, once you’ve figured it out, it is easy to manage.

I use Dict 0 for all of my stable code. Embedded in the file are the commands to store the code in flash.

My in progress programs are uploaded in to ram, and I use commands in the file to do this as well. For example:

\ color-term - dictionary for color coding terminal
forgetram

\ xterm colors 256!
\ https://github.com/sindresorhus/xterm-colors
: esc 27 emit ;
: black      ( -- cursor colour )  esc ." [38;5;0m" ;
: red        ( -- cursor colour )  esc ." [38;5;9m" ;
: green      ( -- cursor colour )  esc ." [38;5;2m" ;
: purple     ( -- cursor colour )  esc ." [38;5;93m" ;
: blue       ( -- cursor colour )  esc ." [38;5;12m" ;
: magenta    ( -- cursor colour )  esc ." [38;5;127m" ;
: cyan       ( -- cursor colour )  esc ." [38;5;51m" ;
: white      ( -- cursor colour )  esc ." [38;5;15m" ;
: grey       ( -- cursor colour )  esc ." [38;5;8m" ;
: fuchsia    ( -- cursor colour )  esc ." [38;5;13m" ;
: green3     ( -- cursor colour )  esc ." [38;5;34m" ;
: lime       ( -- cursor colour )  esc ." [38;5;10m" ;
: navy       ( -- cursor colour )  esc ." [38;5;4m" ;
: darkorange ( -- cursor colour )  esc ." [38;5;208m" ;
: grey62     ( -- cursor colour )  esc ." [38;5;247m" ;
: grey82     ( -- cursor colour )  esc ." [38;5;252m" ;

: test_black      black      ." BLACK black " black ;
: test_red        red        ." RED red " black ;
: test_green      green      ." GREEN green " black ;
: test_purple     purple     ." PURPLE purple " black ;
: test_blue       blue       ." BLUE blue " black ;
: test_magenta    magenta    ." MAGENTA magenta " black ;
: test_cyan       cyan       ." CYAN cyan" black ;
: test_white      white      ." WHITE white " black ;
: test_grey       grey       ." GREY grey " black ;
: test_fuchsia    fuchsia    ." FUCHSIA fuchsia " black ;
: test_green3     green3     ." GREEN3 green3 " black ;
: test_lime       lime       ." LIME lime " black ;
: test_navy       navy       ." NAVY navy " black ;
: test_darkorange darkorange ." DARKORANGE darkorange " black ;
: test_grey62     grey62     ." GREY62 grey62 " black ;
: test_grey82     grey82     ." GREY82 grey82 " black ;

: colors
        cr test_black cr test_grey cr test_grey62 cr test_grey82
        cr test_white cr test_red cr test_darkorange cr test_lime
        cr test_green3 cr test_green cr test_cyan cr test_blue
        cr test_navy cr test_magenta cr test_fuchsia cr test_purple
        cr
;

: bp fuchsia cr . .s cr black ;

Note the forgetram at the begining of the file. When this file is uploaded, it will first delete the contents of RAM (the earlier version of this file) and will replace it with this version. Once I believe it is solid, I will add it to Dict0 or have it as a separate dictionary.

The best way to determine where the code is stored is where it is postioned in list:

list
 ram-based --- Mecrisp-Stellaris RA 2.6.5 --- flash-based  ok.

Words prior to the Mecrisp-Stellaris… are in RAM and will be lost on reset or power off. Words after, are in Flash and will persist.

Other Forth Transfer Examples (Older information)

I reviewed upload.c, xfr.py and ff_shell.py, then created my own (albeit Frankenstein version) of an upload program. Some notes as to what I liked about each:

upload.c

Jan does a nice job of documenting the patches required to the Mecrisp-Stellaris code. He makes specific changes to the interpreter and datastackandmacro code so that it can be more efficient with the uploader. I haven’t incorporated all of his changes, I use a different acknowledgement scheme for now. He uses ACK/NAK which is a great way to do it and I’m going to look at for my code in the future.

xfr.py

While attempting to fix some things in upload.c, I ran across this note by Piotr regarding an absurdly simple program xfr.py:

#!/usr/bin/env python3
import sys
for l in open(sys.argv[1], "rt"):
    sys.stdout.write(l)
    r = sys.stdin.readline()
    sys.stderr.write(r)
    if not r.endswith("ok.\n"):
        sys.exit(1)

You can invoke this using picocom:

picocom -b 115200 /dev/ttyACM0 --imap lfcrlf,crcrlf --omap delbs,crlf --send-cmd "./xfr.py"

And it works, quite well! This source on Github is his more fleshed out version. It works extremely well and was my solution for a period of time.

However, once I finally understood how to increase the RP2040 baud rate, I wasn’t able to run picocom at 921600 on my Mac. Which meant, I needed a more full-feature version, one that didn’t require a terminal program.

ff_shell.py

This version works well with a previous version of Forth, called FlashForth. I really like FF, however, Mecrisp-Stellaris Forth is significantly more advanced and supported so I’ve made the move.

This program uses PySerial to communicate directly with the board and PySerial was the missing link. Once I began to use PySerial as the basis for communication, it just seemed like the way to go.

Comments powered by Talkyard.