Niccle: an Ethernet 10BASE-T bit banging project

  • Published on
  • • Updated on
  • • 7 min read
  • • Tags: 
  • esp32
  • ethernet
  • niccle
  • rust

I've started a new hobby project which I'm calling Niccle.1 Niccle will be a bit banged implementation of the Ethernet 10BASE⁠-⁠T protocol, written in Rust and initially targeting the ESP32-C6 microcontroller. In this first post about the project I'll set out my goals, and discuss a few of the other similar projects I took inspiration from.

Table of Contents

Project introduction

When I first started tinkering with AVR microcontrollers as a hobbyist almost 14 years ago (thanks, NerdKits!), connecting them directly to the internet was quite the challenge. Especially for beginner hobbyists, you generally would have to rely on a large additional piece of hardware (such as Arduino's Ethernet Shield, supporting a whopping 4 simultaneous socket connections!). These were the days before the ESP8266 chips came onto the scene (2014 onwards), which blew the door wide open to building internet-connected DIY projects.

I remember reading about a bit banged Ethernet implementation for AVR microcontrollers back then, and ever since I've always been interested in trying my hand at it myself one day. It only took me close to 15 years to actually get around to it... almost enough time for the Ethernet 10BASE⁠-⁠T standard (published in 1990) to double in age.

Now that I'm ready to give it a try, my goals for the project are as follows:

  • I want to build a software-only bit banged implementation of the 10BASE⁠-⁠T protocol. That is, one that doesn't coopt an auxiliary peripheral like I2S or SPI controllers to perform any of the signaling, at least not initially. Many of the other projects I'm aware of do leverage those, I'm curious to see if I could do without.

  • I'll target my implementation at the ESP32-C6 microcontroller initially, because I have a few development kits lying around, and because it supports a maximum CPU frequency of 160 ⁠MHz and supports high speed GPIO (see my earlier post about their GPIO speed). This seems sufficiently fast for fully software-only implementation, while making it just challenging enough.

  • I'd like to decouple as much of the code as possible from the specific microcontroller and specific signaling methods to leave the door open to targeting other microcontrollers o methods at a later date (e.g. plugging in a peripheral-based implementation as an alternative to the software-only implementation at a later time).

  • I want to be able to transmit as well as receive data, rather than limiting myself to the somewhat simpler task of supporting only outbound data transmission.

  • I want to be able to layer a full TCP/IP stack on top of the implementation, to gain full internet connectivity.

  • I plan to write the project in Rust, because it's a fun and powerful language with a great developer experience and a growing embedded systems ecosystem. Case in point: there's already a great microcontroller-compatible TCP/IP stack available, called SmolTCP, which I intend to leverage.

I started the project with these parameters mostly because I want to learn something new:

  • about how the Ethernet protocol works,

  • about how to stretch a given microcontroller's capabilities to its limits, and how to leverage Rust along the way (most of my previous embedded systems experience was in C/C++), and

  • about the actual electrical engineering aspects of Ethernet (e.g. the circuit design).

Of course the ESP32 family of microcontrollers is known for having built-in Wi-Fi peripherals, so it's probably pretty uncommon for someone to want to add Ethernet connectivity to those chips. That places this project squarely in the "doing it, not because it's useful, but because it's interesting" category. I sure have found it quite interesting so far! And, since the project probably won't be useful to anyone in any real practical sense, I'd at least like to share what I learn along the way in case it is of interest to someone else one day.

Prior art

I'm of course not the first to try my hand at a bit banged Ethernet implementation. Many have done so before me, and the following projects are a few implementations that I took a closer look at before starting my own project:

  • cnlohr/ethertiny (2014): a 10BASE⁠-⁠T implementation targeting an AVR ATTiny85 microcontroller, making use of the chip's Universal Serial Interface (USI) peripheral to perform the signaling at the necessary frequencies. Covered by Hackaday.
  • cnlohr/espthernet (2016): a 10BASE⁠-⁠T implementation by the same author as the previous line, targeting the ESP8266 microcontroller, making use of the I2S peripheral on the device to perform the signaling. Apparently uses a custom TCP/IP stack from their avrcraft project. Covered by Hackaday.
  • holysnippet/pico_eth (2022): a 10BASE-t implementation targeting a Raspberry Pi RP2040 microcontroller, making use of chip's Programmable I/O peripheral. The author layered the lwIP TCP/IP stack on top, making it a complete implementation. Covered by Hackaday.

These are just a few of the ones I looked at in more depth, but there's many others (e.g. BertoldVdb/SPI10M which supports only simplex connections for Raspberry Pi, IgorPlug for AVRs, etc.).

Looking ahead

I plan to structure the project in roughly the following stages:

  • Circuit design. Designing the electrical circuit, and validating it by observing incoming link test pulses and transmitting test pulses ourselves. At this point we should be able to make a connected device think a link has been established.

  • PHY TX layer. Building a first version of the PHY data transmission functionality, and validating it by confirming that the device on the other side can receive transmitted data correctly (e.g. using a hardcoded ARP request packet)

  • PHY RX layer. Building a first version of the PHY data reception functionality, and validating it by confirming that we can receive an incoming packet correctly (e.g. an ARP response packet).

  • MAC layer. Building the MAC layer on top of that, handling preambles, SFDs, and checksums, as well as implementing a rudimentary queue for holding incoming packets.

  • TCP/IP layer. And finally, layering a TCP/IP stack on top the MAC layer, allowing us to use all the features one associates with general internet connectivity: DHCP-based address assignment, full IP connectivity, using socket APIs for programming etc.

In my next post I'll first go into a bit more detail about what the PHY and MAC layers are responsible for, exactly.

I'll use this section to maintain a list of posts related to this project, serving as a directory, if you will.

The posts published so far are:

  1. Niccle: a new project for bit banging Ethernet 10BASE⁠-⁠T (this post).
  2. A high-level overview of Ethernet 10BASE⁠-⁠T.
  3. Designing an electrical circuit for connecting to Ethernet 10BASE⁠-⁠T.
  4. Counting CPU cycles on ESP32-C3 and ESP32-C6 microcontrollers.
  5. Transmitting data at the Ethernet 10BASE⁠-⁠T PHY layer.
  6. Receiving data at the Ethernet 10BASE⁠-⁠T PHY layer.
  7. Implementing and optimizing an Ethernet 10BASE⁠-⁠T MAC layer for maximum throughput (final post).

Footnotes

1

Niccle: because if we had a nickel for each time someone started another useless bit banging project, we'd all be rich! And... because what I'm trying to build would, if successful, be akin to the world's worst NIC.