# t2-firmware: the SPI bridge on Tessel 2's coprocessor

> The C firmware for the Atmel SAM D21 that sits beside the MT7620n system on chip in a Tessel 2. It drives the two module ports, carries CLI traffic over USB, and implements a three pipe SPI bridge whose protocol is shaped around a half duplex controller.

**tessel/t2-firmware** — Firmware for Tessel 2's SAMD21 coprocessor and related SoC code

- Repository: https://github.com/tessel/t2-firmware
- Stars: 100 · Forks: 39
- Language: C
- License: NOASSERTION
- Published: 2026-08-17 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/tessel-t2-firmware

## The coprocessor is a peripheral controller, not the computer

The Atmel SAM D21 on a Tessel 2 has a specific list of jobs, and reading it tells you why the design is split in two. It controls the GPIO, SPI, UART, I2C and ADC interfaces of the two module ports on behalf of the system on chip, which is an MT7620n running Linux. It transfers data and commands between USB and that Linux system so the Tessel CLI can talk to it. It provides a USB serial console for the SoC, which is how you get a shell during development. It programs the SoC's SPI flash over USB. And it manages power state, both for the SoC and for the module ports. So the Linux side never touches the module buses directly; everything goes through the microcontroller, which is what makes the port behaviour uniform and what makes this firmware the place to look for a pin or a bus bug.

## Six directories, each with one job

The directory structure is the quickest way to understand the scope. firmware/ holds the main SAMD21 firmware source. common/ holds utilities for the SAMD21 peripherals along with board-specific headers. deps/ holds dependency submodules, named as Atmel headers and a USB stack, which is why the clone command has to be recursive. boot/ is the USB DFU bootloader, which is the code that makes the flashing process in the README possible at all. soc/ is a bridge daemon that runs on the SoC and communicates with the microcontroller over SPI, so the Linux side of the bridge is a program and the microcontroller side is firmware. node/ holds Node libraries for controlling the module ports through the MCU, which is what an application on the device actually links against. Six directories, and the boundary between firmware and daemon is the bridge protocol.

## The SPI bridge borrows USB's shape, with three pipes

The bridge between the MT7620n and the SAMD21 is modelled loosely on USB, and that analogy does most of the explaining. It provides three bidirectional channels between Unix domain sockets in the Linux environment on the SoC and functions inside the MCU firmware. Pipe 0 is connected to a pair of USB endpoints and carries Tessel CLI communication with the Linux system, which is why the CLI can drive the device without a separate channel. Pipes 1 and 2 are the control paths for the two Tessel module ports, one pipe per port. The transport underneath is plain SPI with four lines, MOSI, MISO, SCK and CS1, where the SoC is master and the MCU is slave, plus two handshake pins: SYNC, driven low by the SoC during setup and high during data, and IRQ, driven high by the MCU when it wants to be polled because it has data to send or is ready to receive.

## The SoC's SPI controller is half duplex, and the protocol admits it

The README flags the constraint that shapes everything else: the MT7620 SPI controller was designed only to talk to SPI flash and is not full duplex, and the protocol designs around that limitation. A transaction therefore has a setup phase and an optional data phase. The SoC brings SYNC low, the MCU prepares a DMA chain for the setup transfer, and each side supplies four things: a magic number to verify correct operation, bits specifying which channels are connected, bits specifying which channels it is ready to accept data on, and one byte per channel giving the data length it is ready to send. Once that is exchanged both sides can compute what the data transfer will contain, and the SoC raises SYNC to begin it. If a side is not ready, or sends zero length, that channel-direction is ignored for the transaction and the bits are repeated in later transfers until the other side shows up. The data payload itself carries the channel payloads in channel order with no framing, because the framing was derived from the setup.

## Each port has its own queue, and some replies jump the line

Above the bridge sits the port command queue. Each port has an independent queue, reached through a Unix domain socket on the Linux side, and software running on the device, Node or anything else, can submit a batch of actions that travel in a single bridge transaction and are executed in order, with replies coming back the same way. The exception is documented rather than hidden: some replies, a pin change interrupt or a UART receive, are inserted asynchronously into the stream of in-order replies, so a consumer cannot assume strict ordering for those. The stated direction of travel is larger batches. The eventual goal is for the SoC to send bigger command batches or macros that execute in real time, isolated from both the Linux preemptive scheduler and the Node garbage collector, which is a latency problem as much as a throughput one.

## Four targets, one Makefile, and a version string from git

The build is a plain Makefile with four targets, and reading it tells you what ships. TARGETS is firmware, boot, test_rig and test_rig_boot, and each one is described in its own .mk file that the Makefile includes by suffix, so build.mk, boot.mk, common.mk, firmware.mk, usb.mk and the test rig equivalents hold the per-target sources, flags, linker script and defines. The toolchain is named explicitly: arm-none-eabi-gcc for compiling, arm-none-eabi-objcopy to turn the ELF into a binary with the eeprom region removed, and arm-none-eabi-size for reporting. A generated version.c is rebuilt whenever .git/HEAD or the index changes, with the value taken from git describe --long, so a binary reports the commit it was built from. The Atmel paths point into deps/sam0, and the Makefile also carries clean, which removes the build directory, and update, which runs git submodule update --init --recursive.

## Building needs the ARM toolchain, installed two documented ways

Building the firmware requires the gcc-arm-embedded toolchain, and there are two installations given, one per platform. On a Mac with Homebrew it is a tap plus an install:

```
brew tap tessel/tools
brew install gcc-arm
```

On Ubuntu 14.04 or 14.10 the README points at the gcc-arm-embedded personal package archive:

```
sudo add-apt-repository ppa:terry.guo/gcc-arm-embedded && sudo apt-get update
sudo apt-get install git gcc-arm-none-eabi
```

With the toolchain in place the build is three commands, and the clone is recursive because of the dependency submodules:

```
git clone https://github.com/tessel/t2-firmware --recursive
cd t2-firmware
make
```

The result lands in a build directory as both an ELF for debugging and a binary for flashing, which is the pair the update commands expect.

## Flashing is a button hold, and t2 update has a footgun

To put the board into bootloader mode you plug the USB port into your computer while holding down the button next to the Tessel 2 logo, and the power LED blinks. The preferred way to write firmware is the CLI, installed with npm i -g t2-cli, and the update for the coprocessor binary is:

```
t2 update --firmware-path build/firmware.bin
```

The OpenWrt image on the SoC is a separate flag and a separate step, and the warning is the part to remember: if you provide neither a firmware path nor an OpenWrt path, t2-cli defaults to pulling remote images, so a forgotten argument silently replaces the binary you just built. The legacy route is dfu-util against device id 1209:7551, whose output in the README includes an Invalid DFU suffix signature warning that the text dismisses as harmless. The same note applies to scope: this only updates the SAMD21. For hardware debugging there is an SWD path, a 0.05 inch header soldered on J401 next to port A, a Bus Blaster v3 flashed for SWD, and arm-none-eabi-gdb against the built ELF through openocd.

## Conclusion

t2-firmware fits someone working on Tessel 2 hardware, whether they are adding a peripheral, debugging the bridge or rebuilding the bootloader, because the README documents the wire protocol at a level of detail that is rare in firmware projects. Three things to know first. The build needs the ARM embedded toolchain, installed through Homebrew or an Ubuntu package archive, and the repository has to be cloned with submodules because the Atmel headers and USB stack live in dependencies. The bridge is deliberately shaped around a half duplex SPI controller on the SoC, so anything you add to the protocol has to respect that. And the flash step has a footgun worth remembering, since t2-cli pulls remote images when you omit both path flags, which quietly replaces the binary you just built. Nothing has been pushed to master since 4 November 2018.

## FAQ

### What does the Tessel 2 firmware do?

It is the firmware for the Atmel SAM D21 coprocessor. It controls the two module ports' GPIO, SPI, UART, I2C and ADC interfaces from the SoC, transfers data and commands between USB and the SoC for the Tessel CLI, provides a USB serial console, programs the SoC's SPI flash over USB, and manages power state.

### How do I build the Tessel 2 firmware?

Install the gcc-arm-embedded toolchain through Homebrew or the Ubuntu package archive, then clone the repository recursively, change into it and run make. The Makefile builds four targets, firmware, boot, test_rig and test_rig_boot, with arm-none-eabi-gcc.

### How do I flash new firmware to a Tessel 2?

Plug in USB while holding the button next to the Tessel 2 logo, which puts the board in bootloader mode with the power LED blinking. Then install t2-cli with npm i -g t2-cli and run t2 update --firmware-path build/firmware.bin, or use the legacy dfu-util command against device id 1209:7551.

### What happens if I run t2 update without a path flag?

If you do not provide a firmware path or an OpenWrt path, t2-cli defaults to pulling remote images, so it will not flash the binary you just built. Pass --firmware-path for the SAMD21 binary or --openwrt-path for the SoC image.

### Does updating the SAMD21 firmware update the SoC?

No. The flash step only updates the firmware on the SAMD21 coprocessor. OpenWrt on the SoC has to be updated separately, using the t2-cli update path.

## Sources

- [Official README](https://github.com/tessel/t2-firmware#readme)
- [Project repository](https://github.com/tessel/t2-firmware)
- [Release notes](https://github.com/tessel/t2-firmware/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tessel-t2-firmware
