Getting started with the ClusterHAT

Posted on Tue 25 March 2025 in OS

Getting started with the ClusterHAT

The ClusterHAT is a HAT connecting four Pi Zeros to a base Raspberry Pi. It is controlled using the clusterctrl cmdl tool, and provides SD or USB booting for the Zeros, as well as networking the Zeros via their built-in USB gadget mode. Unfortunately, the only in-depth documentation is its source code. From this, we can gather the following:

  • The hat itself is controlled via I²C
  • USB booting the SD-less nodes uses the official usbboot tool
  • The post-boot file system is served via NFS

In this part, we'll have a look at controlling the hat via I²C. Luckily, 9front has built-in support via the #J kernel driver. (Hint: check your builtin drivers by reading /dev/drivers) The hat should be configured at I²C address 20. If everything is bound correctly, accessing the hat is thus a matter of reading/writing to the /dev/i2c1/i2c.20.data.

I make sure that the hat is present and the namespace is configured correctly by running this function:

int
opendata(void)
{
    if (access("/dev/i2c1/i2c.20.data", 0) != 0){
        if (bind("#J20", "/dev", MBEFORE) < 0){
            sysfatal("Cannot detect ClusterHat");
        }
    }
    int fd = open("/dev/i2c1/i2c.20.data", ORDWR);
    if (fd < 0){
        sysfatal("cannot open i2c.20.data file");
    }
    return fd;
}

Issuing commands is then a mere matter of writing bytes to this file:

uchar cmd[8];
void
sendcmd(int data, uchar c, uchar v)
{
    cmd[0] = c;
    cmd[1] = v;
    pwrite(data, cmd, 2, 0);
}

The GPIO expander chips used on the ClusterHAT are XRA1200(P)s. The meaning of their available control registers is neatly documented in their datasheet. In our case, besides the initial setup (reset + config all pins to output), everything is controlled via the OCR (output control register).

Enabling the Pi at port P1 is thus achieved via this code:

void
setport(int data, uchar port, int v)
{
    uchar ocr = readreg(data, OCR);
    if (v) {
        sendcmd(data, OCR, ocr | port);
    } else {
        sendcmd(data, OCR, ocr & (0xFF - port));
    }
}

setport(data, P1, 1);

As my first dive into both I²C and Plan 9 development, I can only say that the typical file-based abstraction is a breeze to work with. There's no special API to learn, just one set of basic system-level tools that work the same for text files and this device driver. Awesome!

Find the full first iteration of my 9front version of clusterctl, able to turning the USB ports on and off, here: Download