Pin addresses (PinRef)
Every switch, knob and light in your sketch needs to know where its wire is plugged in. You tell
it with a pin address, written PinRef(...). You'll write one for every wire on your panel,
so it's worth a few minutes to understand how they work before anything else.
It's an address
The easiest way to think of a pin address is as a postal address. A wire on the board itself is
like a house: it has one name and one place, so its address is short — PinRef(PA0). A wire on
an expander is more like an apartment, where you need the building, the floor and
the door — PinRef(expander1, PORT_A, 0).
The control is the mail carrier. It delivers to whatever address you give it, and it doesn't care whether that address is a house or an apartment. That's why every switch, knob and light works the same way no matter where you plug it in: you only ever change the address.
Every kind of address
| The wire goes to… | You write | Example |
|---|---|---|
| A pin on the board | PinRef(pin name) |
PinRef(PA0) |
| A digital expander | PinRef(expander, port, pin) |
PinRef(expander1, PORT_B, 2) |
| A shift-register chain | PinRef(ShiftBus1, chip, pin) |
PinRef(ShiftBus1, 0, 3) |
| An analog expander | PinRef(adc, input) |
PinRef(adc1, 0) |
For a pin on the board, use the name printed next to it, such as PA0. On an expander, the
numbers count from 0, so PinRef(expander1, PORT_B, 2) is the expander's pin GPB2, and
PinRef(ShiftBus1, 0, 3) is pin 3 on the first chip in the chain.
Moving a wire? Change one line
Here's the same switch plugged in three different places. Click through the tabs and you'll see
that only the address changes — the Switch2Pos line is identical every time.
const PinRef MASTER_ARM_PIN = PinRef(PA0);
OpenSkyhawk::Switch2Pos masterArm(DCSIN_ARM_MASTER, MASTER_ARM_PIN);
const PinRef MASTER_ARM_PIN = PinRef(expander1, PORT_A, 0);
OpenSkyhawk::Switch2Pos masterArm(DCSIN_ARM_MASTER, MASTER_ARM_PIN);
const PinRef MASTER_ARM_PIN = PinRef(ShiftBus1, 0, 0);
OpenSkyhawk::Switch2Pos masterArm(DCSIN_ARM_MASTER, MASTER_ARM_PIN);
Name your addresses at the top
It's a good habit to give every address a name and keep them all together at the top of your sketch, one line per wire, named after whatever is on the end of it:
// ── Where everything is wired ──────────────────────────────
const PinRef MASTER_ARM_PIN = PinRef(PA0);
const PinRef GEAR_HANDLE_PIN = PinRef(expander1, PORT_A, 1);
const PinRef CAUTION_LED_PIN = PinRef(ShiftBus1, 0, 4);
That way, if you ever move a wire, there's exactly one line to fix. The block also doubles as your wiring list when you come to build the harness.
Which controls work where
| Board pin | Digital expander | Shift register | Analog expander | |
|---|---|---|---|---|
| Switches and buttons | ✅ | ✅ | ✅ | — |
| Rotary encoders | ✅ | ✅ | ✅ best | — |
| Knobs and sliders | ✅ analog pins | — | — | ✅ |
| Lights | ✅ | ✅ | ✅ | — |
| Gauge needles | ✅ | ✅ slower | ✅ | — |
| Dimmable backlights | ✅ PWM pins | — | — | — |
The gaps come down to what each kind of pin can actually sense. An analog expander measures how far something is turned, so it can't reliably read a switch that is simply on or off. A digital expander or a shift register is the opposite: it only sees on or off, so it can't tell where a knob is pointing. Dimming a light needs a pin that can switch about a thousand times a second, and only some of the board's own pins can do that — they're marked on the board.
Which controls are in each row?
- Switches and buttons:
Switch2Pos,Switch3Pos,SwitchMultiPos,ActionButton,SwitchWithCover2Pos - Rotary encoders:
RotaryEncoder,RotaryAcceleratedEncoder - Knobs and sliders:
AnalogInput,AnalogMultiPos,AngleSensorInput - Lights:
LED - Gauge needles:
NeedleGauge - Dimmable backlights:
Dimmer
Two outputs don't need a pin address at all. DrumDisplay plugs straight into an I²C
cable, and IntegerOutput hands its value to your own code.
Going further
PIN_NC means "nothing connected". You'll use it for an optional pin you don't need, such
as a gauge's home sensor.
Some controls check their address when the board starts up. A Dimmer given an expander
pin, for example, prints a message on the debug port and stays off, rather than half-working
and leaving you guessing.
Everything a PinRef can do is in the API reference.