-
Notifications
You must be signed in to change notification settings - Fork 43
Developing and deploying Punyforth applications
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.
$ 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.
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.
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.
- The list command only works well if you flashed the esp using --block-format yes parameter.
- If you're not intended to use the screen editor don't use the block-format parameter as it adds some extra startup time
- When you load something from the flash the /end word tells Punyforth to stop loading because it reached the end of the given module
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.
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.
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.
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
Attila Magyar