Skip to content

machine/attiny85: add USI-based SPI support#5181

Merged
deadprogram merged 8 commits intotinygo-org:devfrom
jespino:attiny85-spi-support
Jan 17, 2026
Merged

machine/attiny85: add USI-based SPI support#5181
deadprogram merged 8 commits intotinygo-org:devfrom
jespino:attiny85-spi-support

Conversation

@jespino
Copy link
Copy Markdown
Contributor

@jespino jespino commented Jan 16, 2026

Summary

Implement SPI communication for ATtiny85 using the USI (Universal Serial Interface) hardware in three-wire mode. The ATtiny85 lacks dedicated SPI hardware but can emulate SPI using the USI module with software clock strobing.

Implementation

  • Configure USI in three-wire mode for SPI operation
  • Use clock strobing technique to shift data in/out
  • Pin mapping: PB2 (SCK), PB1 (MOSI/DO), PB0 (MISO/DI)
  • Support both Transfer() and Tx() methods
  • Software-based frequency control via delay loops
  • All 4 SPI modes supported
  • LSB-first bit order supported via software bit reversal

The implementation uses the USI control register (USICR) to toggle the clock pin, which triggers automatic bit shifting in hardware.

Frequency Configuration

The ATtiny85 USI lacks hardware prescalers, so frequency is controlled via software delay loops between clock toggles:

// Configure SPI at 1 MHz
machine.SPI0.Configure(machine.SPIConfig{
    Frequency: 1000000,
})

// Configure SPI at 400 kHz (useful for SD card initialization)
machine.SPI0.Configure(machine.SPIConfig{
    Frequency: 400000,
})

// Configure SPI at maximum speed (no delay)
machine.SPI0.Configure(machine.SPIConfig{
    Frequency: 0,
})

SPI Mode Configuration

All 4 SPI modes are supported:

Mode CPOL CPHA Clock Idle Data Sampled
0 0 0 Low Rising edge
1 0 1 Low Falling edge
2 1 0 High Falling edge
3 1 1 High Rising edge
// Configure SPI in Mode 0 (default)
machine.SPI0.Configure(machine.SPIConfig{
    Mode: machine.Mode0,
})

// Configure SPI in Mode 3
machine.SPI0.Configure(machine.SPIConfig{
    Mode: machine.Mode3,
})

Bit Order Configuration

Both MSB-first (default) and LSB-first bit orders are supported:

// Configure SPI with LSB-first bit order
machine.SPI0.Configure(machine.SPIConfig{
    LSBFirst: true,
})

LSB-first is implemented via software bit reversal since the USI hardware only supports MSB-first.

References

  • tinySPI library - reference implementation
  • ATtiny85 datasheet USI section

Implement SPI communication for ATTiny85 using the USI (Universal Serial
Interface) hardware in three-wire mode. The ATTiny85 lacks dedicated SPI
hardware but can emulate SPI using the USI module with software clock
strobing.

Implementation details:
- Configure USI in three-wire mode for SPI operation
- Use clock strobing technique to shift data in/out
- Pin mapping: PB2 (SCK), PB1 (MOSI/DO), PB0 (MISO/DI)
- Support both Transfer() and Tx() methods

The implementation uses the USI control register (USICR) to toggle the
clock pin, which triggers automatic bit shifting in hardware. This is
more efficient than pure software bit-banging.

Current limitations:
- Frequency configuration not yet implemented (runs at max software speed)
- Only SPI Mode 0 (CPOL=0, CPHA=0) supported
- Only MSB-first bit order supported

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Co-authored-by: Ona <no-reply@ona.com>
@jespino jespino force-pushed the attiny85-spi-support branch from 14a2504 to d88634f Compare January 16, 2026 09:04
Add software-based frequency control for USI SPI. The ATtiny85 USI lacks
hardware prescalers, so frequency is controlled via delay loops between
clock toggles.

- Calculate delay cycles based on requested frequency and CPU clock
- Fast path (no delay) when frequency is 0 or max speed requested
- Delay loop uses nop instructions for timing control

Co-authored-by: Ona <no-reply@ona.com>
@jespino
Copy link
Copy Markdown
Contributor Author

jespino commented Jan 16, 2026

I have a problem about the Modes here, I can try to implement them but I don't have SPI devices with other modes, also, I don't have an SPI device for reading. Maybe I need somebody helping me with that.

jespino and others added 6 commits January 16, 2026 09:38
Add support for all 4 SPI modes (Mode 0-3) using USI hardware:
- Mode 0 (CPOL=0, CPHA=0): Clock idle low, sample on rising edge
- Mode 1 (CPOL=0, CPHA=1): Clock idle low, sample on falling edge
- Mode 2 (CPOL=1, CPHA=0): Clock idle high, sample on falling edge
- Mode 3 (CPOL=1, CPHA=1): Clock idle high, sample on rising edge

CPOL is controlled by setting the clock pin idle state.
CPHA is controlled via the USICS0 bit in USICR.

Co-authored-by: Ona <no-reply@ona.com>
Add software-based LSB-first support for USI SPI. The USI hardware only
supports MSB-first, so bit reversal is done in software before sending
and after receiving.

Uses an efficient parallel bit swap algorithm (3 operations) to reverse
the byte.

Co-authored-by: Ona <no-reply@ona.com>
Test the USI-based SPI implementation for ATtiny85/digispark.

Co-authored-by: Ona <no-reply@ona.com>
Reduce SPI struct from ~14 bytes to 1 byte to fit in ATtiny85's limited
512 bytes of RAM.

Changes:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin management (user must handle CS)
- Remove frequency control (runs at max speed)
- Remove LSBFirst support

The SPI struct now only stores the USICR configuration byte.

Co-authored-by: Ona <no-reply@ona.com>
This reverts commit 387ccad.

Co-authored-by: Ona <no-reply@ona.com>
Remove unnecessary fields from SPI struct while keeping all functionality:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin (user must manage it, standard practice)

Kept functional fields:
- delayCycles for frequency control
- usicrValue for SPI mode support
- lsbFirst for bit order support

SPI struct reduced from 14 bytes to 4 bytes.

Co-authored-by: Ona <no-reply@ona.com>
@jespino jespino marked this pull request as ready for review January 17, 2026 11:54
@deadprogram
Copy link
Copy Markdown
Member

I see the LED flickering but it does not seem to be communicating with my MCP3008.

@jespino
Copy link
Copy Markdown
Contributor Author

jespino commented Jan 17, 2026

I see the LED flickering but it does not seem to be communicating with my MCP3008.

@deadprogram is this not working in any mode (mode 0 or mode 3) or is not working in mode 3 only? I have the max7218 working properly in mode 1, but is only for output, so it is a very limited test. Other option is to buy myself an mcp3008 and try it here.

@deadprogram
Copy link
Copy Markdown
Member

Works in mode 0, so I think we're good! Thanks @jespino do you want to squash some commits here?

@jespino
Copy link
Copy Markdown
Contributor Author

jespino commented Jan 17, 2026

I'm ok if you squash and merge through the GitHub interface 🙂. I already ordered a mcp3008 to test it, so I'll make it work for mode 3 in a subsequent PR.

@deadprogram
Copy link
Copy Markdown
Member

Thank you @jespino now squash/merging.

@deadprogram deadprogram merged commit 5d8e071 into tinygo-org:dev Jan 17, 2026
19 checks passed
deadprogram pushed a commit that referenced this pull request Mar 23, 2026
* machine/attiny85: add USI-based SPI support

Implement SPI communication for ATTiny85 using the USI (Universal Serial
Interface) hardware in three-wire mode. The ATTiny85 lacks dedicated SPI
hardware but can emulate SPI using the USI module with software clock
strobing.

Implementation details:
- Configure USI in three-wire mode for SPI operation
- Use clock strobing technique to shift data in/out
- Pin mapping: PB2 (SCK), PB1 (MOSI/DO), PB0 (MISO/DI)
- Support both Transfer() and Tx() methods

The implementation uses the USI control register (USICR) to toggle the
clock pin, which triggers automatic bit shifting in hardware. This is
more efficient than pure software bit-banging.

Current limitations:
- Frequency configuration not yet implemented (runs at max software speed)
- Only SPI Mode 0 (CPOL=0, CPHA=0) supported
- Only MSB-first bit order supported

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: add SPI frequency configuration support

Add software-based frequency control for USI SPI. The ATtiny85 USI lacks
hardware prescalers, so frequency is controlled via delay loops between
clock toggles.

- Calculate delay cycles based on requested frequency and CPU clock
- Fast path (no delay) when frequency is 0 or max speed requested
- Delay loop uses nop instructions for timing control

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: add SPI mode configuration support

Add support for all 4 SPI modes (Mode 0-3) using USI hardware:
- Mode 0 (CPOL=0, CPHA=0): Clock idle low, sample on rising edge
- Mode 1 (CPOL=0, CPHA=1): Clock idle low, sample on falling edge
- Mode 2 (CPOL=1, CPHA=0): Clock idle high, sample on falling edge
- Mode 3 (CPOL=1, CPHA=1): Clock idle high, sample on rising edge

CPOL is controlled by setting the clock pin idle state.
CPHA is controlled via the USICS0 bit in USICR.

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: add LSB-first bit order support

Add software-based LSB-first support for USI SPI. The USI hardware only
supports MSB-first, so bit reversal is done in software before sending
and after receiving.

Uses an efficient parallel bit swap algorithm (3 operations) to reverse
the byte.

Co-authored-by: Ona <no-reply@ona.com>

* GNUmakefile: add mcp3008 SPI example to digispark smoketest

Test the USI-based SPI implementation for ATtiny85/digispark.

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: minimize SPI RAM footprint

Reduce SPI struct from ~14 bytes to 1 byte to fit in ATtiny85's limited
512 bytes of RAM.

Changes:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin management (user must handle CS)
- Remove frequency control (runs at max speed)
- Remove LSBFirst support

The SPI struct now only stores the USICR configuration byte.

Co-authored-by: Ona <no-reply@ona.com>

* Revert "machine/attiny85: minimize SPI RAM footprint"

This reverts commit 387ccad.

Co-authored-by: Ona <no-reply@ona.com>

* machine/attiny85: reduce SPI RAM usage by 10 bytes

Remove unnecessary fields from SPI struct while keeping all functionality:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin (user must manage it, standard practice)

Kept functional fields:
- delayCycles for frequency control
- usicrValue for SPI mode support
- lsbFirst for bit order support

SPI struct reduced from 14 bytes to 4 bytes.

Co-authored-by: Ona <no-reply@ona.com>

---------

Co-authored-by: Ona <no-reply@ona.com>
deadprogram pushed a commit that referenced this pull request Apr 5, 2026
* machine/attiny85: add USI-based SPI support

Implement SPI communication for ATTiny85 using the USI (Universal Serial
Interface) hardware in three-wire mode. The ATTiny85 lacks dedicated SPI
hardware but can emulate SPI using the USI module with software clock
strobing.

Implementation details:
- Configure USI in three-wire mode for SPI operation
- Use clock strobing technique to shift data in/out
- Pin mapping: PB2 (SCK), PB1 (MOSI/DO), PB0 (MISO/DI)
- Support both Transfer() and Tx() methods

The implementation uses the USI control register (USICR) to toggle the
clock pin, which triggers automatic bit shifting in hardware. This is
more efficient than pure software bit-banging.

Current limitations:
- Frequency configuration not yet implemented (runs at max software speed)
- Only SPI Mode 0 (CPOL=0, CPHA=0) supported
- Only MSB-first bit order supported

* machine/attiny85: add SPI frequency configuration support

Add software-based frequency control for USI SPI. The ATtiny85 USI lacks
hardware prescalers, so frequency is controlled via delay loops between
clock toggles.

- Calculate delay cycles based on requested frequency and CPU clock
- Fast path (no delay) when frequency is 0 or max speed requested
- Delay loop uses nop instructions for timing control

* machine/attiny85: add SPI mode configuration support

Add support for all 4 SPI modes (Mode 0-3) using USI hardware:
- Mode 0 (CPOL=0, CPHA=0): Clock idle low, sample on rising edge
- Mode 1 (CPOL=0, CPHA=1): Clock idle low, sample on falling edge
- Mode 2 (CPOL=1, CPHA=0): Clock idle high, sample on falling edge
- Mode 3 (CPOL=1, CPHA=1): Clock idle high, sample on rising edge

CPOL is controlled by setting the clock pin idle state.
CPHA is controlled via the USICS0 bit in USICR.

* machine/attiny85: add LSB-first bit order support

Add software-based LSB-first support for USI SPI. The USI hardware only
supports MSB-first, so bit reversal is done in software before sending
and after receiving.

Uses an efficient parallel bit swap algorithm (3 operations) to reverse
the byte.

* GNUmakefile: add mcp3008 SPI example to digispark smoketest

Test the USI-based SPI implementation for ATtiny85/digispark.

* machine/attiny85: minimize SPI RAM footprint

Reduce SPI struct from ~14 bytes to 1 byte to fit in ATtiny85's limited
512 bytes of RAM.

Changes:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin management (user must handle CS)
- Remove frequency control (runs at max speed)
- Remove LSBFirst support

The SPI struct now only stores the USICR configuration byte.

* Revert "machine/attiny85: minimize SPI RAM footprint"

This reverts commit 387ccad.

* machine/attiny85: reduce SPI RAM usage by 10 bytes

Remove unnecessary fields from SPI struct while keeping all functionality:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin (user must manage it, standard practice)

Kept functional fields:
- delayCycles for frequency control
- usicrValue for SPI mode support
- lsbFirst for bit order support

SPI struct reduced from 14 bytes to 4 bytes.

---------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants