Skip to content

Developing and deploying Punyforth applications

Attila Magyar edited this page May 13, 2018 · 24 revisions

A simple blinker application

In this tutorial we're going to develop a simple Punyforth application that will blink a led.

Open a text editor and enter the following program.

( This is a simple Punyforth application that blinks a LED )
GPIO load \ load GPIO module first otherwise we won't be able to use GPIO related words

\ This constant defines the GPIO pin that is attached to a LED
13 constant: LED \ D7 leg on a nodemcu devboard

\ Let's define a new word that will blink the LED 10 times
: start-blinking ( -- )
    println: 'Starting blinking LED'
    LED 10 times-blink
    println: 'Stopped blinking LED' ;

\ execute the previously defined word
start-blinking

Save the file as blinker.forth.

Flashing

$ cd arch/esp8266/bin
$ python flash.py /dev/ttyUSB4 --main blinker.forth

This command will flash the binaries and the source code of Punyforth modules.

After you reset the esp, you should see the led blinking 10 times. If you attach a serial terminal to the esp, you'll be able to invoke the start-blinking again, and see the printouts.

Loading modules

If you run the following command you'll see the available modules. In the blink example we've already used one of them (GPIO).

$ python flash.py --help

Modules can be loaded with the load word. This resembles the import statement from other programming languages.

For example:

(stack) DECOMP load
...
(stack) decompile: abs
16r3FFE96F4:  <entercol>
16r3FFE96F8:  dup                                                
16r3FFE96FC:  0<                                                 
16r3FFE9700:  branch0    ->                                      
16r3FFE9704:  12           |                                     
16r3FFE9708:  invert       |                                     
16r3FFE970C:  1+           |                                     
16r3FFE9710:  <exit>     <-                                      
(stack) 

This will load the decompiler. You can execute this interactively in the REPL or put it somewhere inside your program (typically at the beginning of the file). It will load the source code of the module from the flash and compile it into the memory of the esp. Due to the limited amount of available memory, it is advised to check how much free space is left after loading a module.

You can do this by using the freemem ( -- n ) word. This gives back the available Forth dictionary space (or heap memory) in bytes.

(stack) freemem
(stack 13088) 

If you run out of free space your esp will likely crash.

How does everything work

Read this only if you're interested in some of the internal implementation details.

Your code (blinker.forth) is stored in the 82th sector of the flash memory (0x52000 address).

Let's flash our blinker.forth again, but this time with an additional --block-format yes parameter.

$ python flash.py /dev/cu.wchusbserial1420 --main blinker.forth --block-format yes

This command will reformat the source code and arranged as 32 lines, each with 128 columns (by padding it with spaces). This makes easier to display and edit the stored code on the esp (in the cost of some extra loading time).

We can see the content of the 82th sector by using the list ( n -- ) word. But first we need to load the FLASH module.

(stack) FLASH load
..
(stack) 82 list
 0 83       load                                                                                                                  
 1 81 load                                                                                                                        
 2 ( This is a simple Punyforth application that blinks a LED )                                                                   
 3 GPIO load \ load GPIO module first otherwise we won't be able to use GPIO related words                                        
 4                                                                                                                                
 5 \ This constant defines the GPIO pin that is attached to a LED                                                                 
 6 13 constant: LED \ D7 leg on a nodemcu devboard                                                                                
 7                                                                                                                                
 8 \ Let's define a new word that will blink the LED 10 times                                                                     
 9 : start-blinking ( -- )                                                                                                        
10     println: 'Starting blinking LED'                                                                                           
11     LED 10 times-blink                                                                                                         
12     println: 'Stopped blinking LED' ;                                                                                          
13                                                                                                                                
14 \ execute the previously defined word                                                                                          
15 start-blinking                                                                                                                 
16                                                                                                                                
17 stack-show                                                                                                                     
18 /end                                                                                                                           
19 

As you can see, our blinker program is indeed stored in the 82th sector.

You can edit the current screen by using the r: parsing word.

(stack) 4 r: println: 'Adding this new line to row 4th'
(stack) flush

This will overwrite the given row of the current screen with the given string. Try listing the code again and you should see the updated version. If you restart the esp you'll see the extra line printed out.

If you looked the output of list carefully you might noticed the two extra lines which were automatically prepended to your code. The first line loads the core module from the 83th sector (this address is not fixed, it depends how large your program is). This module defines Punyforth core words, including control structures like if, do loops, while loops, etc.

The second line loads some code from the 81th sector. Let's find out what this is about.

(stack) 81 list
 0 95 constant: DHT22                                                                                                             
 1 98 constant: FLASH                                                                                                             
 2 100 constant: FONT57                                                                                                           
 3 102 constant: GPIO                                                                                                             
 4 104 constant: MAILBOX                                                                                                          
 5 105 constant: NETCON                                                                                                           
 6 109 constant: NTP                                                                                                              
 7 111 constant: PING                                                                                                             
 8 113 constant: SONOFF                                                                                                           
 9 115 constant: SSD1306I2C                                                                                                       
10 121 constant: SSD1306SPI                                                                                                       
11 129 constant: TASKS                                                                                                            
12 133 constant: TCPREPL                                                                                                          
13 136 constant: TURNKEY                                                                                                          
14 137 constant: WIFI                                                                                                             
15 139 constant: EVENT                                                                                                            
16 140 constant: RINGBUF                                                                                                          
17 143 constant: DECOMP                                                                                                           
18 146 constant: PUNIT                                                                                                            
19 /end                                                                                                                           
20   

This code describes where each module is located on the flash. These addresses are not fixed, they depend on the size of each module.

You can load any of these with the load word as we did before, just be aware of the limited amount of memory and don't load anything twice as it'll eventually eat up the dictionary space.

Few notes

  1. The list command only works well if you flashed the esp using --block-format yes parameter.
  2. If you're not intended to use the screen editor don't use the block-format parameter as it adds some extra startup time
  3. When you load something from the flash the /end word tells Punyforth to stop loading because it reached the end of the given module

Terminal settings

Use these settings for CoolTerm (Cross-platform with GUI) or Termite (Windows with GUI):

Baud rate: 115200 bps. 
Local echo: on
Line mode: enabled

Picocom (command line terminal) should work too with these settings:

picocom /dev/ttyUSB4 -b 115200 --omap crlf --echo --imap delbs

Although I recommend using either Termite or CoolTerm because picocom sends characters over as you type them in so you cannot use backspace to correct typos.

Sending files without persistently storing in flash

If you just want to send some code without storing it in the flash memory of the esp, you can use the following command (or you can use your favorite serial terminal application with file upload support).

cat file.forth > /dev/ttyUSB4

This will send the content of file.forth to the given serial port. But first run the above picocom command to have the port configured in the appropriate way.

For example

Open picocom in a terminal window.

picocom /dev/ttyUSB4 -b 115200 --omap crlf --echo --imap delbs

Execute the following in a different terminal window.

echo ": hello ( -- ) println: 'Hello World' ;" > hello.forth
cat hello.forth > /dev/ttyUSB4

Now if you type hello in picocom, you should see the 'Hello World' message.

If you restart or crash the esp and try to call the hello world again, you'll get an undefined word error.

Using markers to forget words

Because of the Forth Hyper Static Global Environment a redefinition of a word won't remove the pervious version of the word.

: myword println: 'ver1' ;
freemem . cr \ prints out the available dictionary space

: myword println: 'ver2' ;
freemem . cr \ the available dictionary space is smaller than before

: myword println: 'ver3' ;
freemem . cr \ the available dictionary space is smaller than before

If you don't want to run out of memory during development, you'll need to learn about the marker: word. This word creates a restorable checkpoint.

To be more precise, the marker: word creates a new word whose execution semantics are to remove itself and everything defined after it.

\ print out available dictionary space
freemem . cr

\ create a marker whose name is -checkpoint
marker: -checkpoint

\ this definition will consume some of the dictionary space
: hello println: 'hello world' ; 
hello
freemem . cr \ dictionary space is less than before

\ Forget every definitions that were made after the marker (including the marker)
-checkpoint  

hello \ hello is no longer defined
freemem . cr \ available dictionary space is same as before

If you keep sending code during development put this 2 lines in the beginning of the file to avoid running out of memory.

-checkpoint
marker: -checkpoint