
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

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!
;

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!
;

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!
;

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!
;

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