Mecrisp-Stellaris Forth: Notes

6 minute read

Where I compile interesting notes as to how Mecrisp-Stellaris Forth is implemented on the RP2040 microcontroller.


I was working on this entry and was attempting to hand-write the definition of PIN_BLINK by keeping the stack in my head. As my definition approached over 10 albeit short lines, it continued to not work. This is when I remembered, Forth is designed to be easy to read and interactive, work with Forth, not against it!

So I started writing small words which accomplish a very specific task. Done in 10 minutes.

RAM vs. Flash

In a note Matthias noted “I parted the available 256 kb of main RAM into 20 kb for Forth core, 44 kb for “compiletoflash” mode and 192 kb for “compiletoram” mode” as he had more use for RAM as a data logger than for program storage. Based on a request, he created a second version where the memory was more evenly split. As a comparison, his original version shows freeram of 195,368 Bytes, while the new split shows 129,832 Bytes available. The lower number for RAM means there is more room available for program storage.

Free Ram

In this entry, same as above, Terry shows how to determine the amount of free RAM. The word is defined:

: freeram ( -- )
  flashvar-here 4 -
  compiletoram here
  - . ." Bytes "

Flash Dictionaries

One can save an absurd amount of code (and possibly data) to the 8MB Flash memory on the Feather board. MSForth will save dictionaries of size 44KB at a time using save# ( n – ). The initial dictionary is considered dictionary image “0” and if it contains the INIT word, that word will be executed on startup. Be careful with definitions inside the INIT word as improperly defined, debugged or designed words could put the board into an unplanned infinite loop, requiring a hard reset with new code being loaded. Here are the words for handling dictionaries:

  new   ( -- )   Clear the current RAM copy of the "flash dictionary" and restart Forth
  save  ( -- )   Save  the current RAM copy of the "flash dictionary" to the first image in SPI flash
                 This is the same as 0 save#

  load#  ( u -- ) Load the u-th image of the "flash dictionary" from the SPI into RAM and restart Forth
  save#  ( u -- ) Save the current RAM copy of the "flash dictionary" to the u-th image in SPI flash
  erase# ( u -- ) Erase the u-th image from the SPI flash

I’m experimenting with having dict0 be my fully debugged, ready to go words, while dict1 is where I’m testing things, (and I want to keep them.) This means dict0 is loaded on reset and I load dict1 when I’m ready to start testing. Once I’m comfortable with the code, I’ll move the code over to dict0.

The with tools code has a significant dictionary 0, be sure to add to it, and not erase it.

Dict0 - debugged and ready to go

I keep a running dictionary of words that have been tested and worked. Given the size can be 44KB of compiled code, I’m comfortable stuffing everything I write in Dict0, until I run out of room.

My process is:

  1. Reboot Feather into USB mode by press/hold BOOT/SEL, press/release RESET, then release BOOT/SEL.
  2. Copy latest uf2 (with tools) over to the board.
  3. Copy Dictionary 0 using drag and drop to Serial program
  4. Press reset and if concerned, enter words4 to show the complete set of words.

Feather LED Issue

I saw the blinky code which was part of the MSForth package and attempted to run it. No matter how hard I tried, it would not get the built-in LED (pin 13) to blink. There was a comment that stated “Already configured in core for SIO (Software IO), function 5”. Which sent me down a datasheet rat-hole for a few hours…

Net, net, every pin except pin13 was set to SIO otherwise known as F5 which is the required setting for a PIO pin to act as a PIO pin. See RP2040 Datasheet, pages 14, 258 and 264. “SIO: Software control of GPIO, from the single-cycle IO (SIO) block. The SIO function (F5) must be selected for the processors to drive a GPIO, but the input is always connected, so software can check the state of GPIOs at any time.”

I’m not upset, as to the hours spent tracking down this issue. I needed to spend time to understand how the PIO works as a standard issue GPIO port (on the RP2040 called SIO). It was either now or later.

I did create two words which are handy to me:

: pin_ctrl ( pin -- ) \ get the address for the specific pin ctrl register

\ print values of the GPIO_CTRL registers
: print_CTRL ( n -- ) \ print CTRL values upto pin n
	0 CR DO 
	I pin_ctrl @
	I . . CR

Stack Contents

Mecrisp-Stellaris will put 42 (the answer to everything) on the stack - 1. This means if there is a stack underflow, this number will change. It’s helpful to check the stack periodically for the following reasons:

  1. Are the contents of the stack, what you expect?
  2. Is the depth of the stack, what you expect?
  3. If the depth is 0, is TOS: 42? If not, then you have a stack error in your code!
  4. “reset” will reset everything…and put 42 back where it needs to be.

Changing the Baud Rate

Here are the details as to changing the baud rate: Fractional baud rate divider (RP2040 Datasheet pg 440)


I made the change in: mecrisp-stellarisLK-2.5.9a-128k/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 baud rate divisor is a 22-bit number consisting of a 16-bit integer and a 6-bit fractional part. This is used by the baud rate generator to determine the bit period. The fractional baud rate divider enables the use of any clock with a frequency >3.6864MHz to act as UARTCLK, while it is still possible to generate all the standard baud rates.

The 16-bit integer is written to the Integer Baud Rate Register, UARTIBRD. The 6-bit fractional part is written to the Fractional Baud Rate Register, UARTFBRD. The Baud Rate Divisor has the following relationship to UARTCLK: Baud Rate Divisor = UARTCLK/(16√óBaud Rate) = where is the integer part and is the fractional part separated by a decimal point as Figure 60. You can calculate the 6-bit number ( ) by taking the fractional part of the required baud rate divisor and multiplying it by 64 (that is, , where is the width of the UARTFBRD Register) and adding 0.5 to account for rounding errors:

m = int(FBRD * 64 + 0.5)

An internal clock enable signal, Baud16, is generated, and is a stream of one UARTCLK wide pulses with an average frequency of 16 times the required baud rate. This signal is then divided by 16 to give the transmit clock. A low number in the baud rate divisor gives a short bit period, and a high number in the baud rate divisor gives a long bit period.


Baud rate of 115200 = 67, 52 WORKS

125000000/115200/16 = 67.81684028 67.81684028 - 67 = 0.81684028 52 = 52 52 * 64/10000 + .5 = 0.8328 0.81684028 - 0.8328 = -0.01595972

Baud rate of 460,800 = 16, 71 WORKS

115200 * 4 = 460,800 125000000/460,800/16 = 16.95421007 16.95421007 - 16 = 0.95421007 71 = 71 71 * 64/10000 + .5 = 0.9544 0.95421007 - 0.9544 = -0.00018993

Baud rate of 921,600 = 8, 77 WORKS

115200 * 8 = 921,600 125000000/921,600/16 = 8.47710503 8.47710503 - 8 = 0.47710503 77 = 77 1 * 64/10000 + .5 = 0.9928 0.47710503 - 0.9928 = -0.51569497

Comments powered by Talkyard.