Forth SPI on Arduino

One byte (0xa6) transmitted from master out SPI pin using Forth

Arduino-FVM comes with only a few Arduino-function words, basically just a few for working with the digital pins. So the test for Forth was going to be: how difficult is it to access other microchip functionality using memory reads and writes? An encouraging first step was to implement SPI TX, using the information in the 328 datasheet.

C Code Example from 328 datasheet of SPI Master TX

First I needed some constants for the register addresses and bit numbers:

0x24 constant DDR_SPI
0x3 constant DD_MOSI
0x5 constant DD_SCK
0x2 constant DD_SS
0x4c constant SPCR
0x6 constant SPE
0x4 constant MSTR
0x0 constant SPR0
0x4e constant SPDR
0x4d constant SPSR
0x7 constant SPIF

Next I needed to set up the data direction bits for the MOSI, SCK, and SS pins, as well as set some bits in the SPCR register to control mode and communication frequency:

: setup-master-spi ( -- )
1 DD_MOSI lshift
1 DD_SCK lshift
1 DD_SS lshift
or or
1 SPE lshift
1 MSTR lshift
1 SPR0 lshift
or or

And here is a function for transmitting a single byte, after dropping the byte on the stack:

: tx-master ( ch -- )
SPSR c@ 1 SPIF lshift and

And here is a demo procedure for sending byte 0xA6 repeatedly:

: spi-demo

Now we can see that byte on the oscilloscope, from MOSI pin 11:

SPI signal for byte 0xA6

Byte 0xA6 equals 10100110, which you can understand from the signal image if you know that (1) signal low represents “1” and high represents “0”; (2) the X-scale is 2 microseconds, with one bit per microsecond; and (3) the first bit is the one microsecond of low signal just to the left of the Y axis.


Arduino FORTH: Register Access

Edit after more research: the approach below might not be best possible approach when working with GPIO pins, as it might not be taking advantage of special opcodes for this purpose. Also, due to the two byte FVM cell size, I think there might be some potential for strange effects in nearby registers of a different type (like, the PINC register being after the PORTB register). Heading back to the data sheets…

Edit 2: It looks like FORTH (including Arduino-FVM) has the C@ and C! commands which are single-byte (char) versions of @ and !.

The Atmel specific keywords that come with Arduino-FVM are rather limited – basically just PINMODE, DIGITALREAD, and DIGITALWRITE. So, to do much of interest you would want to start playing around with the atmel microprocessor registers. This is mostly straightforward, however, as the registers are accessible simply as locations in ram, meaning you just write or read bits to ram to get what you want. Doing this in FORTH, you need a few tools

  • @ word: read from a memory location
  • ! word: write to a memory location
  • HEX word: switch to hexidecimal interpretation of the stack input/output (DECIMAL to switch back).
  • Pinout for your arduino board (available on the internet)
  • Data sheet for your specific microcontroller (available on the internet)

Here is a quick example of lighting up the LED through a register write. First, we need to know our pin number and also which register we are dealing with, available from the pinout.

Arduino Nano Pinout

There we have pin number 13 (decimal) for the LED, and register PB5. First, to keep this post a bit shorter, we we simply use the PINMODE keyword to set pin 13 to output mode:

true 13 pinmode

Now we look at the datasheet to get the memory location for register PB5.

A page from 328P Datasheet

So, PORTB register is at 0x25 (hex) and bit five is what we want. Now, let’s see what is already in that memory location. On my chip, I got:

0x25 @ . 0

Just keep in mind however, as you can see using the CELL command, that you have actually read in two bytes. So, if you get something like 0x2000, you are only interested in the first byte (the right-most 00).

As a general practice, I only want to write to the bit of interest, preserving other bit states. So, I drop a 1 for that bit on the stack, and use an OR to light up just that bit. A 1 in bit 5 is hex 0x20.

0x25 @ 0x20 or 0x25 ! 

And the LED lights up. We can see now what we have in register memory:

0x25 @ . 20

That is a trivial example, but I think you would need to go into this memory “peeking” and “poking” in order to do more advanced things like control the interrupt system or special purpose pins. At least, I find the idea more appealing than having to write C interface functions on the backend. Of course, you would want to hide the details inside nice forth functions.

Functions as parameters in FORTH

I was looking for something like the lambda in FORTH. It turns out there is the single quote operator which puts the “execution token” of a word on the stack, which you can EXECUTE later.

To give a practical example of such use, here is a maparray function:

: maparray ( xt a- n -- )
        dup 0 >
            -rot ( n1 xt a- )
            dup @ ( n1 xt a- n2 )
            swap ( n1 xt n2 a- )
            -rot ( n1 a- xt n2 )
            swap ( n1 a- n2 xt )
            dup >r ( n1 a- n2 xt, xt )
            execute ( n1 a- n3, xt )
            over ( n1 a- n3 a-, xt )
            >r ( n1 a- n3, xt a- )
            swap ( n1 n3 a-, xt a- )
            ! ( n1, xt a- )
            1 - ( n4, xt a- )
            r> ( n4 a-, xt )
            cell + ( n4 a2-, xt )
            swap ( a2- n4, xt )
            r> ( a2- n4 xt )
            -rot ( xt a2- n4 )
    drop drop drop

With this function, I can pass in the execution token, an array address, and the length of the array (in cells) and the execution token is executed on each array element, with the results stored back in the array.

christopher@nightshade ~/Repos/forth-learning$ gforth func.fs
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
create a 1 , 2 , 3 , 4 , 5 , .s <0>  ok
a 5 cells dump                 
7FCF3CC56378: 01 00 00 00  00 00 00 00 - 02 00 00 00  00 00 00 00  ................
7FCF3CC56388: 03 00 00 00  00 00 00 00 - 04 00 00 00  00 00 00 00  ................
7FCF3CC56398: 05 00 00 00  00 00 00 00 -                           ........
: foo 1 + ;  ok                
' foo a 5 maparray  ok         
a 5 cells dump                 
7FCF3CC56378: 02 00 00 00  00 00 00 00 - 03 00 00 00  00 00 00 00  ................
7FCF3CC56388: 04 00 00 00  00 00 00 00 - 05 00 00 00  00 00 00 00  ................
7FCF3CC56398: 06 00 00 00  00 00 00 00 -                           ........

So, basically, the maparray function takes a function that acts on one element, and foo here is a function that adds one to an element, and I pass foo into maparray.

Fibonacci in gforth

Working through some FORTH exercises from the gforth tutorial, here is a function which generates the nth Fibonacci number:

: fib ( n1 n2 n3 -- n.
    compute n3'th fib number, index starting at zero
    first two args are starting nums, 0 1 for normal fib seq )
        dup 2 >
            -rot ( n3 n1 n2 )
            dup ( n3 n1 n2 n2 )
            >r ( n3 n1 n2 )
            + ( n3 n4 )
            r> ( n3 n4 n2 )
            swap ( n3 n2 n4 )
            rot ( n2 n4 n3 )
            1 -
    dup 0 = if
        drop drop
        dup 1 = if
            drop swap drop
            drop +


christopher@nightshade ~/Repos/forth-learning$ ./ 
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
0 1 0 fib . 0  ok
0 1 1 fib . 1  ok
0 1 2 fib . 1  ok
0 1 3 fib . 2  ok
0 1 4 fib . 3  ok
0 1 10 fib . 55  ok
0 1 50 fib . 12586269025  ok
0 1 70 fib . 190392490709135  ok

I can do, say, the 1000000th number and get a near instant result. But the integers start wrapping around long before that, so the result is not useful.

The above code can be done in a more recursive style with the RECURSE keyword, but there is not an automatic tall-call optimization, so that crashes the stack on large numbers.

A feature of the above code is use of the return stack, with the >R and R> words, to briefly save a value, so I don’t have to move the stack around so much.