FlashForth SPI (328P)

example 0: byte 0xDE sent using SPI (green) and SCK driver signal (yellow). Common SPI configuration.

I wrote some some FlashForth code to be able to initialize the SPI system on the 328P, and transmit bytes – a more thorough version of what I had done earlier with Arduino-FVM. It basically amounted to a list of SPI-related register and bit constants.

\ ff-328p.fs

\ Copyright 2020 Christopher Howard

\ Licensed under the Apache License, Version 2.0 (the "License");
\ you may not use this file except in compliance with the License.
\ You may obtain a copy of the License at

\     http://www.apache.org/licenses/LICENSE-2.0

\ Unless required by applicable law or agreed to in writing, software
\ distributed under the License is distributed on an "AS IS" BASIS,
\ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\ See the License for the specific language governing permissions and
\ limitations under the License.

ff-328p
marker ff-328p

\ General I/O port addresses

$25 constant PORTB
$28 constant PORTC
$2b constant PORTD

\ Data Direction Registers

$24 constant DDRB
  1 constant DD_OUT
  0 constant DD_IN

\ DDR for SPI comms

$24 constant DDR_SPI
$2 constant DD_SS
$3 constant DD_MOSI
$5 constant DD_SCK

\ SPI control register

$4c constant SPCR
$0 constant SPR0 \ SPI Clock Rate Selector bits
$1 constant SPR1 \ (see table 18-5 in 328P datasheet)
$2 constant CPHA \ Clock Phase bit
  1 constant CPHA_SMPTRL \ sample on trailing edge of SCK
  0 constant CPHA_SMPLED \ sample on leading edge of SCK
$3 constant CPOL \ Clock Polarity bit
  1 constant CPOL_HIDLE \ SCK high when idle
  0 constant CPOL_LIDLE \ SCK low when idle
$4 constant MSTR \ Master/Slave Select bit
  1 constant MSTR_MSTR \ master mode
  0 constant MSTR_SLAVE \ slave mode
$5 constant DORD \ Data Order bit
  1 constant DORD_LSB \ LSB transmitter first
  0 constant DORD_MSB \ MSB transmitted first
$6 constant SPE \ SPI Enable bit
  1 constant SPE_ENAB \ SPI enabled
  0 constant SPE_DISAB \ SPI disabled
$7 constant SPIE \ SPI Interrupt Enable bit
  1 constant SPIE_ENAB \ SPI Interrupt Enabled
  0 constant SPIE_DISAB \ SPI Interrupt Disabled

\ SPI status register

$4d constant SPSR 
$0 constant SPI2X \ Double SPI Speed Bit
$6 constant WCOL \ Write COLlision Flag
$7 constant SPIF \ SPI Interrupt Flag

\ SPI Data Register (i/o port)

$4e constant SPDR

: tx-spi ( c -- )
    SPDR c! begin SPSR c@ 1 SPIF lshift and until
;

Here are some examples:

\ a common configuration
: init-spi-ex0 ( -- )
    \ set data direction bits
    DD_OUT DD_MOSI lshift
    DD_OUT DD_SCK  lshift or
    DD_OUT DD_SS   lshift or DDR_SPI c!
    \ Setup control register
    1           SPR0 lshift
    0           SPR1 lshift or \ fck/16
    CPHA_SMPLED CPHA lshift or
    CPOL_LIDLE  CPOL lshift or
    MSTR_MSTR   MSTR lshift or 
    DORD_MSB    DORD lshift or
    SPE_ENAB    SPE  lshift or
    SPIE_DISAB  SPIE lshift or SPCR c!
;

\ send byte repeatedly with 1 ms delay in between
: rpt-spi-char ( c -- )
    begin dup tx-spi 1 ms again ;

init-spi-ex0  ok<#,ram> 
$de rpt-spi-char
example 0: a common configuration

Here we are transmitting with a 1Mhz clock (fck/16). We have the SCK set to idle on low voltage, with the sample on the lead edge. This means when you see the yellow signal jump up, look down at the green line to see the bit – high for 1 and low for 0. Here we have 11011110, i.e., value 0xDE in Most Significant bit (MSB) order, as expected.

In this slight variation, we sample on the trailing edge of the SCK signal instead:

\ with clock phase sampled on trailing
: init-spi-ex1 ( -- )
    \ set data direction bits
    DD_OUT DD_MOSI lshift
    DD_OUT DD_SCK  lshift or
    DD_OUT DD_SS   lshift or DDR_SPI c!
    \ Setup control register
    1          SPR0 lshift
    0          SPR1 lshift or \ fck/16
    CPHA_SMPTRL CPHA lshift or
    CPOL_LIDLE CPOL lshift or
    MSTR_MSTR  MSTR lshift or 
    DORD_MSB   DORD lshift or
    SPE_ENAB   SPE  lshift or
    SPIE_DISAB SPIE lshift or SPCR c!
;
example 1: sample on trailing edge of SCK

So to determine the bit value, you must look at the value below where the yellow SCK signal falls down to low.

Another variation is to go back to sampling on the leading edge, but have SCK idle on high, so again we must look for the SCK signal to fall down to low.

\ clock polarity high on idle
: init-spi-ex2 ( -- )
    \ set data direction bits
    DD_OUT DD_MOSI lshift
    DD_OUT DD_SCK  lshift or
    DD_OUT DD_SS   lshift or DDR_SPI c!
    \ Setup control register
    1           SPR0 lshift
    0           SPR1 lshift or \ fck/16
    CPHA_SMPLED CPHA lshift or
    CPOL_HIDLE  CPOL lshift or
    MSTR_MSTR   MSTR lshift or 
    DORD_MSB    DORD lshift or
    SPE_ENAB    SPE  lshift or
    SPIE_DISAB  SPIE lshift or SPCR c!
;
example 2: SCK idle on high

In another variation, we can go back to the common configuration, with SCK idle on low, and looking for the leading edge (rise in SCK signal), but instead transmit the bits in Least Significant Bit (LSB) order:

\ LSB data order
: init-spi-ex3 ( -- )
    \ set data direction bits
    DD_OUT DD_MOSI lshift
    DD_OUT DD_SCK  lshift or
    DD_OUT DD_SS   lshift or DDR_SPI c!
    \ Setup control register
    1           SPR0 lshift
    0           SPR1 lshift or \ fck/16
    CPHA_SMPLED CPHA lshift or
    CPOL_LIDLE  CPOL lshift or
    MSTR_MSTR   MSTR lshift or 
    DORD_LSB    DORD lshift or
    SPE_ENAB    SPE  lshift or
    SPIE_DISAB  SPIE lshift or SPCR c!
;
example 3: transmit bits in LSB order

Now you see the bits are 01111011, which reverses to 11011110, i.e., 0xDE.

Our final demonstration variation is the common configuration but at a much slower speed, fck/128, or 125,000 bits/sec in our case.

\ much slower speed
: init-spi-ex4 ( -- )
    \ set data direction bits
    DD_OUT DD_MOSI lshift
    DD_OUT DD_SCK  lshift or
    DD_OUT DD_SS   lshift or DDR_SPI c!
    \ Setup control register
    1           SPR0 lshift
    1           SPR1 lshift or \ fck/128
    CPHA_SMPLED CPHA lshift or
    CPOL_LIDLE  CPOL lshift or
    MSTR_MSTR   MSTR lshift or 
    DORD_MSB    DORD lshift or
    SPE_ENAB    SPE  lshift or
    SPIE_DISAB  SPIE lshift or SPCR c!
;
example 4: slower speed

You see the waveform is the same except one SCK cycle is taking a full 8 microseconds.

FlashForth Question: Sending a File

A question recently received from a new FlashForth user:

> The (theoretically) simple thing I'm struggling with is: How do I
> transmit a file for compilation. I google the !/&§" out of it, but
> can't find a solution.
> Working with a linux system, I've tried putty, moserial, minicom and
> more, but none allows me to tranfer a file w/o errors.
> Just working through the terminal works great, jut uploading words is
> not possible. Handshake is on (hard- and soft).
> How do you do that?

My response:

As best as I can figure it, it seems that for some reason FlashForth cannot receive characters while it is processing the previous line of input from the terminal, so some characters are lost if you simply paste a file to the terminal. However, the shell that comes with FlashForth is evidently designed to work around this limitation, waiting for the correct time to send each line. In the FlashForth files there is a directory called shell which contains a python script called ff-shell.py. After you start it, you can use the #send <filename> command to send a file. There are a number of other handy commands in that shell for interacting with FlashForth.

A few caveats about the FlashForth shell:

(1) The script is a python2 script, and you must have the pyserial python library install (the python2 version of it).

(2) #send will try to add the extension “.txt” to whatever filename you give it, which is annoying. I fixed this by commenting out lines 175-176 in the source code.

          if len(args) > 3:
            stopString = args[3]
          # if pathfile.endswith(".txt") == False:
          #   pathfile = pathfile + ".txt"
          line = ""

First Foray into KiCad

KiCad is a free software tool for designing Printed Circuit Boards, and can generate files to use with services like PCBWay. PCB manufacturing is getting so cheap now that it is hard to justify all the time and bother of old-fashioned soldering in a project with more than a dozen traces.

KiCad is a bit intimidating at first because of the many pages of the manual and tutorials, and a large forest of minor details. But I think it is not so scary once you understand the basic workflow:

1 – Put together a schematic. This shows the basic types of components (“symbols”) in your circuit and how they interconnect.

Somewhat embarrassing first schematic using KiCad.

2 – Assign “footprints” to your symbols. Footprints basically are the actual hardware you want to match to the component type.

3 – Use the new PCB tool to generate a PCB based on your schematic. You need (in a simple design) to lay out the traces and ground plane on your front and back copper layers, and then lay out the shape of your board in the edge cut layer.

Simple PCB design in KiCad

You can then view the PCB in 3D view if you wish.

Simple PCB design in 3D View

One hundred dollars in Monopoly money to the first person who can figure out what that circuit does!

And finally you generate Gerber files, which can be submitted to a service like PCBWay. You pay for the service, and they send the board with traces, holes, pads, labeling, and artwork as specified. You get the components separately and solder those on yourself.

First Foray into FlashForth for AVR

Arduino-FVM was a nice, easy introduction to Forth on AVR/Arduino, and has a few advantages as a Forth implementation. But I was interested in something more developed and full-featured. So today I got FlashForth installed on a 328P MC, running on an Arduino Uno.

Installing FlashForth on AVR requires setting fuse bits as well as burning a new flash image, so I had to wire up my Nano ArduinoISP programmer again. Pulling this off required mixing details from the ArduinoISP tutorial, my previous post on ArduinoISP, and instructions from the FlashForth Web site, none of which were quite sufficient information taken individually.

I used the precompiled 328P hex flash image that is included in the FlashForth git repo. I intend at some point to compile the source, but I needed to get over the initial hurdle of seeing if I could get FlashForth to run on an MC at all, and if I liked it. This was the avrdude call which worked for me:

christopher@theoden ~/Repos/flashforth/avr/hex$ avrdude -p m328p -c stk500v1 -P /dev/ttyUSB0 -b 19200 -e -u -U flash:w:328-16MHz-38400.hex:i -U efuse:w:0xff:m -U hfuse:w:0xda:m -U lfuse:w:0xff:m

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: erasing chip
avrdude: reading input file "328-16MHz-38400.hex"
avrdude: writing flash (32524 bytes):

Writing | ################################################## | 100% 0.00s

avrdude: 32524 bytes of flash written
avrdude: verifying flash memory against 328-16MHz-38400.hex:
avrdude: load data flash data from input file 328-16MHz-38400.hex:
avrdude: input file 328-16MHz-38400.hex contains 32524 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.00s

avrdude: verifying ...
avrdude: 32524 bytes of flash verified
avrdude: reading input file "0xff"
avrdude: writing efuse (1 bytes):

Writing | ################################################## | 100% 0.01s

avrdude: 1 bytes of efuse written
avrdude: verifying efuse memory against 0xff:
avrdude: load data efuse data from input file 0xff:
avrdude: input file 0xff contains 1 bytes
avrdude: reading on-chip efuse data:

Reading | ################################################## | 100% 0.01s

avrdude: verifying ...
avrdude: 1 bytes of efuse verified
avrdude: reading input file "0xda"
avrdude: writing hfuse (1 bytes):

Writing | ################################################## | 100% 0.02s

avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xda:
avrdude: load data hfuse data from input file 0xda:
avrdude: input file 0xda contains 1 bytes
avrdude: reading on-chip hfuse data:

Reading | ################################################## | 100% 0.01s

avrdude: verifying ...
avrdude: 1 bytes of hfuse verified
avrdude: reading input file "0xff"
avrdude: writing lfuse (1 bytes):

Writing | ################################################## | 100% 0.01s

avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xff:
avrdude: load data lfuse data from input file 0xff:
avrdude: input file 0xff contains 1 bytes
avrdude: reading on-chip lfuse data:

Reading | ################################################## | 100% 0.01s

avrdude: verifying ...
avrdude: 1 bytes of lfuse verified

avrdude done.  Thank you.

Here is the logon using picocom:

christopher@theoden ~/Repos/flashforth/avr/hex$ picocom -b 38400 -c /dev/ttyACM0picocom v3.1

port is        : /dev/ttyACM0
flowcontrol    : none
baudrate is    : 38400
parity is      : none
databits are   : 8
stopbits are   : 1
escape is      : C-a
local echo is  : yes
noinit is      : no
noreset is     : no
hangup is      : no
nolock is      : no
send_cmd is    : sz -vv
receive_cmd is : rz -vv -E
imap is        : 
omap is        : 
emap is        : crcrlf,delbs,
logfile is     : none
initstring     : none
exit_after is  : not set
exit is        : no

Type [C-a] [C-h] to see available commands
Terminal ready
E FlashForth 5 ATmega328 18.11.2020

Here are the words defined by default:

words 
p2+ pc@ @p hi d. ud. d> d< d= d0< d0= dinvert d2* d2/ d- d+ dabs ?dnegate dnegate s>d rdrop endit next for in, inline repeat while again until begin then else if zfl pfl xa> >xa x>r dump .s words >pr .id ms ticks r0 s0 latest state bl 2- ['] -@ ; :noname : ] [ does> postpone create cr [char] ihere ( char ' lit abort" ?abort ?abort? abort prompt quit true false .st inlined immediate shb interpret 'source >in tiu tib ti# number? >number ud/mod ud* sign? digit? find immed? (f) c>n n>c @+ c@+ place cmove word parse \ /string source user base pad hp task ulink rsave bin hex decimal . u.r u. sign #> #s # digit <# hold up min max ?negate tuck nip / u*/mod u/ * u/mod um/mod um* 'key? 'key 'emit p++ p+ pc! p! p@ r>p !p>r !p u> u< > < = 0< 0= <> within +! 2/ 2* >body 2+ 1- 1+ negate invert xor or and - m+ + abs dup r@ r> >r rot over swap drop allot ." ," s" (s" type accept 1 umax umin spaces space 2swap 2dup 2drop 2! 2@ cf, chars char+ cells cell+ aligned align cell c, , here dp ram eeprom flash >< rp@ sp@ 2constant constant 2variable variable @ex execute key? key emit Fcy mtst scan skip n= rshift lshift mclr mset ic, i, operator iflush cwd wd- wd+ pause turnkey to is defer value fl+ fl- c! c@ @ a> ! >a literal int! ;i di ei ver warm empty rx0? rx0 tx0 load- load+ busy idle exit 
marker  ok<#,ram>

I only had a few minutes to experiment with it, but here are a several things of interest:

  • One great thing about FlashForth is that all the three memory types – RAM, flash, and EEPROM, are mapped to a contiguous address space, and accessible with @ and ! memory words. So, you don’t have to do anything special to write to flash or EEPROM.
  • By default, all words you add to the dictionary are compiled to flash memory and therefore survive a chip reset. This is very convenient.
  • You are not actually allowed to redefine words (at least, in the usual way of defining words), but you are expected instead to use the marker Forth word to construct forgettable sections of words. Also the empty word will remove all words but the core FlashForth words.

ANSI Cursor Control from Forth

ANSI graphics in the kitty terminal emulator

When lacking a bit-mapped graphics interface, one old standby is ANSI cursor control, which allows 8 colors, with cursor positioning assuming a monospace font. It could allow you to do limited graphics and forms over a UART connection, e.g., the serial connection to a microcontroller.

An entertaining rabbit trail: Here is Emacs running the pong game in an X11 window (i.e., normal graphics):

Emacs Pong using normal X11 graphics

But if you run Emacs in no-window mode (i.e., “terminal” mode) it looks like it falls back to control codes (ANSI escape codes…?)

Emacs Pong using terminal graphics (control codes, I presume)

Emacs was designed originally to be run in terminal environments which used control codes — bit-mapped graphics were later grafted on to Emacs. This allows Emacs to be run from a Gnu/Linux virtual console (e.g., CTRL-ALT-F2).

Anyway, here is a Forth interface to some of the ANSI escape codes, specifically line,column cursor positioning, and setting a graphics mode (character attributes like color and bold/underline).

\ Copyright 2020 Christopher Howard

\ ansi-cursor.fs

\ Licensed under the Apache License, Version 2.0 (the "License");
\ you may not use this file except in compliance with the License.
\ You may obtain a copy of the License at

\     http://www.apache.org/licenses/LICENSE-2.0

\ Unless required by applicable law or agreed to in writing, software
\ distributed under the License is distributed on an "AS IS" BASIS,
\ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\ See the License for the specific language governing permissions and
\ limitations under the License.

\ http://ascii-table.com/ansi-escape-sequences.php

\ GFX mode values

30 constant black-fg
31 constant red-fg
32 constant green-fg
33 constant yellow-fg
34 constant blue-fg
35 constant magenta-fg
36 constant cyan-fg
37 constant white-fg

40 constant black-bg
41 constant red-bg
42 constant green-bg
43 constant yellow-bg
44 constant blue-bg
45 constant magenta-bg
46 constant cyan-bg
47 constant white-bg

0 constant attributes-off
1 constant bold
4 constant underscore
5 constant blink
7 constant reverse
8 constant concealed

: <esc> 0x1b emit ;

: <[> [char] [ emit ;

: .| abs 0 <# #s #> type ;

: <;> [char] ; emit ;

: position-cursor ( line col -- ) <esc> <[> swap .| <;> .| [char] H emit ;

: erase-display <esc> <[> 2 .| [char] J emit ;

: set-gfx-mode ( gfx-mode-value -- )
    <esc> <[> .| [char] m emit ;

And demo example code:

\ Copyright 2020 Christopher Howard

\ demo.fs

\ Licensed under the Apache License, Version 2.0 (the "License");
\ you may not use this file except in compliance with the License.
\ You may obtain a copy of the License at

\     http://www.apache.org/licenses/LICENSE-2.0

\ Unless required by applicable law or agreed to in writing, software
\ distributed under the License is distributed on an "AS IS" BASIS,
\ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\ See the License for the specific language governing permissions and
\ limitations under the License.

require ansi-cursor.fs

: exp-scale ( n prescaled-range scaled-range -- n )
    rot dup * * swap dup * / ;

: cab-dist ( x y xc yc -- n ) 2 pick - abs >r swap drop - abs r> + ;

: demo-scale ( x y xc yx unscaled-range )
    >r cab-dist r> 8 exp-scale 7 min ;

: demo attributes-off set-gfx-mode erase-display
    80 0 do
        40 0 do
            i j 2dup position-cursor
            20 40 70 demo-scale black-bg + set-gfx-mode
            space
        loop
    loop
    attributes-off set-gfx-mode
    20 31 position-cursor ." * ansi-cursor demo *"
    40 0 position-cursor
;