Think, upload, rinse, repeat May 2017
As often mentioned, Forth enables an interactive development approach: you enter commands on the µC itself, and things get done right away. No make, no compile step, no uploads.
But that’s an over-simplification, because it ignores the fact that most code needs to be written and saved to file. Unless truly one-off of course, and you don’t care about losing the source code once it has run. But usually, we really can’t develop a meaningful application on just the µC.
A more practical development foundation on the µC side is in fact set up like this:
- load the 20 KB Mecrisp core onto the µC (just once, unless flash memory gets messed up)
- add convenience code and drivers, also in flash, i.e.
always / board / core
- use cornerstones to manage flash memory as a stack of word sets
core.fswhen module dependencies change, and reload over the old one
The idea being that all of the above code is fairly stable and well-debugged, so that we don’t have to go back and change this part very often. These are the 500..700 “words” we build on.
Now comes the main part, which can be split into a number of phases:
- thinking about what our project needs to do, and how to gradually map it to code
- designing and building pieces which stand on their own, such as h/w drivers
- trying out snippets of logic, to see what works and figuring out those pesky little details
- putting it all together, i.e. working out potential the interactions and global state
- finalising the code, removing debug statements, and making it start on power-up
Each of these phases is different. Since Forth is a bottom-up system, you can’t try out (or even write) code before the primitive words it uses have been defined. What you could do, is insert dummy word definitions, as a way to ignore some details while working on the bigger picture:
: think ; : edit ; : debug true ; : rinse ; : app think begin edit debug while rinse repeat ; app
The above is a valid Forth application, even if somewhat nonsensical. So one
approach would be to put this code in a source file, say
myapp.fs, and then
use Folie to send it to an attached µC using “
!s myapp.fs”. But that’s a
top-down approach, even if the words are all empty stubs for now: it leads us
to think from the top down and drives the development from that direction.
A more effective approach is to not write this code at all, but just keep it in mind as part of our thought process. Instead, we focus on the pieces that we know we’re going to need, and just start implementing these pieces one by one.
If you’ve set up your project as a sub-directory of the
1608-forth/ area in
Embello (highly recommended), then one way to get going is to set up each new
set of related words as an “exploration”: a source file in an
Uploading to RAM is fastest, avoiding frequent flash erases, so a
starting point for developing the code for say
think is to create an
ex/think.fs file with the following content:
\ this is the think code ... compiletoram? [if] forgetram [then] \ include ../../flib/other/module/you/might/need.fs : think ." Thinking..." ; think
forgetram at the start is very convenient, because it lets us re-send the
source file to replace the previous version. You can ignore the
logic for now. As we start implementing the logic of
think, we end up going
through this process over and over again:
ex/think.fson the host
- save and switch to the terminal window running Folie
!s ex/think.fs(tab-completion means we could also type “
- see what happened, and repeat this cycle by going back to editing
Sometimes (haha: if you’re optimistic …) things won’t work, and the µC may crash or hang. No big deal: hit ctrl-c, and you’re back at the prompt, with all RAM contents conveniently gone.
Occasionally, one of the lower-level words you added won’t be working quite
right. In that case, you can comment out the
think call at the end, and call
the troublesome words interactively, while manually setting up the necessary
arguments. This is where Forth’s interactivity shines: the ability to quickly
try something out, completely ad-hoc.
You can still manually load a changed file, e.g.”
!s ex/think.fs” - even it has
already be added to
core.fs and stored in flash. It will simply generate a few
Redefine warnings. Just keep in mind is that everything compiled
in flash will continue to point to the original version!
If you’re into test-driven development (TDD): have a look at varint.fs and varint-test.fs as examples of how to write test code. By placing that code in a separate file, you’ll be able to run the tests at any time, with the benefit of TDD when it comes to future changes and re-factoring.
If you’re using the multi-tasker, something to watch out for is that
forgetram may cause trouble, since your task might still be referenced in
the task chain. A quick solution is to replace
causes a full re-init instead of just clearing the RAM definitions.
At some point,
ex/think.fs will be deemed stable, and you’ll want to move on to the
next set of words to implement for our application. You can now permanently add
think definition and everything written for it to flash, as follows:
- make a copy of the
core.fsfile in your project area, i.e.
add the following line to it, somewhere before the
<<<core>>>definition at the end:
<<<board>>>to remove the current
core.fsdefinitions from flash
!s core.fsto add your updated version, including the new
From now on, the µC will have the new
think implementation stored in flash, in
the same way as all the other “standard” definitions. You’re ready to move to
the next part of your project.
Note that the integration step comes last. Assuming that same example as before,
at some point, you’ll want to make everything work together. That’s easy: create
dev.fs with that definition of
app given above. Since all the pieces
it relies on have presumably already be implemented and stored in flash (via
core.fs), it should compile just fine.
To start up the completed app, enter the magic spell: “
and if all is well, your application will start running. If not: go back to the
previous mode, and test things in isolation.
Ok, let’s assume everything works as intended. Let’s make the app run on
power-up. No serial port, no development, just running it. Create a file
freeze.fs” with the following contents:
\ install frozen myapp with auto-start on reset include core.fs compiletoflash include dev.fs : init init unattended app ;
Then type “
!s freeze.fs” on the Folie command line. The following will happen:
include core.fs- wipes the old definitions and installs the latest version
compiletoflash- sets up to compile
appitself to flash memory
include dev.fs- compiles that app main loop we just created
: init ... ;- gets redefined to call our application code on power-up
For details on
unattended, see this
article. Note that it won’t start up
app” on reset if you have a serial port connected. In that case, you can
simply type “
app” yourself to have the same effect.
PS. The above approach uses Folie as host
front-end, but this will also work with
picocom and other terminal emulators,
if they can send throttled source code (ctrl-a + “s” in picocom).