Using a buffered serial console May 2016
Mecrisp Forth comes with a serial-port command line interface. This makes both tinkering and uploading new code a breeze, but it’s nevertheless a fairly limited setup:
- no input buffering: if characters come in while the code is busy, they can get lost
- no output buffering: sending any text to the console will block until all data is sent
- the greeting sent to USART1 cannot be changed or redirected (at least in Mecrisp 2.2.7)
- even if the console is reconfigured later on, a reset will still revert to USART1
It’s very easy to redirect console I/O, using a built-in mechanism to re-vector 4 essential words:
key? ( -- Flag ) Checks if a key is waiting
key ( -- Char ) Waits for and fetches the pressed key
emit? ( -- Flag ) Ready to send a character ?
emit ( Char -- ) Emits a character.
This can be done by assigning new handlers to these corresponding 4 variables:
hook-key? ( -- a-addr ) terminal IO
hook-key ( -- a-addr ) on the fly
hook-emit? ( -- a-addr ) Hooks for redirecting
hook-emit ( -- a-addr )
After reset, those variables are set as follows to use USART1 in polled mode:
' serial-key? hook-key? !
' serial-key hook-key !
' serial-emit? hook-emit? !
' serial-emit hook-emit !
If we want to change to an interrupt-based USART2 driver, for which an
implementation has been created
here
and
here,
all we need to do is include those files and add this init
code:
compiletoflash
: init ( -- )
init 1000 ms key? if eraseflash then \ safety escape hatch
+uart-irq
['] uart-irq-key? hook-key? !
['] uart-irq-key hook-key !
['] uart-emit? hook-emit? !
['] uart-emit hook-emit !
cr init ;
This points the input vectors to the interrupt-based driver,
and the output vectors to the (polled) driver for USART2. Note the
compiletoflash
- this code needs to be in flash to survive a reset.
The first line allows recovering from this setup. With “init
”, it’s extremely
important to prepare for the worst, as this code gets called after every reset.
If there is any error in this code, we’ll never get control back! With the
extra line, we can hit a key on USART1 to restore Mecrisp to its original state
and remove this additional init
word.
The above code relies on other code to generate a 1000 ms delay, which is why
there needs to be a call to an earlier init
inside the above code. In
addition, init
is called again just before exit, so that the custom greeting
gets sent to the new console output device, i.e. USART2.
The above works well: on power-up and reset, the console is automatically adjusted to USART2, with all input stored in a ring buffer, so that incoming data is no longer at risk of being dropped.
But there’s still a risk: if we enter any of eraseflash
,
eraseflashfrom
, or flashpageerase
- then we could lose console access
via USART2, since this can wipe out the above init
override.
The simplest solution is: don’t do that… i.e. never enter these commands if you want to keep the console functioning as is. It can be inconvenient, but luckily we can still easily erase the last few definitions in flash and replace them with new ones using a cornerstone, defined as follows:
: cornerstone ( "name" -- ) \ define a flash memory cornerstone
<builds begin here dup flash-pagesize 1- and while 0 h, repeat
does> begin dup dup flash-pagesize 1- and while 2+ repeat cr
eraseflashfrom ;
Followed by this re-definition (thanks to Matthias Koch for this neat suggestion):
cornerstone eraseflash
What this does is to create a “reference point” for clearing flash definitions.
When called, it will partially clear flash memory and remove all definitions
entered after this one. Since this name overrides the earlier definition by
appearing later in the dictionary, it effectively hides that older one. So
from now on, typing eraseflash
will clear flash memory, but keep the USART2
console implementation and the corresponding init
definition.
This does not prevent calling the other two erasing words, but those are harder to use and intended for internal reference anyway (such as in the cornerstone definition itself).
What if we want to revert the code and restore a pristine USART1-based Mecrisp setup? Well… given that all previously-defined words are still present in the dictionary, that’s also possible:
- enter the “
words
” commands and look for the address of the originaleraseflash
word - then enter “
<address> execute
” to run it - or, alternatively… - enter “
$4000|$5000 eraseflashfrom
” (the address depends on the Mecrisp build)
To summarise: with the above tricks, we can make Mecrisp (semi-) permanently use a different console I/O channel (serial or anything else, really), yet still regain control and restore the original polled USART1 implementation when absolutely needed.
The only risk is when we mess up and install an incorrect init
word: in the
worst case, we lose console access for good and can’t recover anymore. At that
point, there will be no other recourse than to re-flash the entire µC memory by
other means (see this summary for some
options).
Note that all of the above could also have been used to turn USART1 into an interrupt-driven & buffered console. USART2 was used as example here because it’s easier during development to switch between two separate interfaces.