Skip to main content
Tutorials

Drawing Letters with LEDs

Overview

This tutorial builds a reusable LedLetter component. The component accepts a capital letter from A to Z, looks up a 5 by 7 bitmap glyph, and uses math to place an LED and current-limiting resistor for every lit pixel.

Each LED string is wired from power through its own resistor and LED to gnd. That keeps the example simple, predictable, and easy to copy into a larger sign, badge, or indicator board.

Final example

type LedLetterProps = {
letter: string
power: string
gnd: string
x?: number
y?: number
pitch?: number
ledFootprint?: string
resistorFootprint?: string
resistance?: string
}

const glyphs: Record<string, string[]> = {
A: ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
B: ["11110", "10001", "10001", "11110", "10001", "10001", "11110"],
C: ["01111", "10000", "10000", "10000", "10000", "10000", "01111"],
D: ["11110", "10001", "10001", "10001", "10001", "10001", "11110"],
E: ["11111", "10000", "10000", "11110", "10000", "10000", "11111"],
F: ["11111", "10000", "10000", "11110", "10000", "10000", "10000"],
G: ["01111", "10000", "10000", "10111", "10001", "10001", "01110"],
H: ["10001", "10001", "10001", "11111", "10001", "10001", "10001"],
I: ["11111", "00100", "00100", "00100", "00100", "00100", "11111"],
J: ["00111", "00010", "00010", "00010", "00010", "10010", "01100"],
K: ["10001", "10010", "10100", "11000", "10100", "10010", "10001"],
L: ["10000", "10000", "10000", "10000", "10000", "10000", "11111"],
M: ["10001", "11011", "10101", "10101", "10001", "10001", "10001"],
N: ["10001", "11001", "10101", "10011", "10001", "10001", "10001"],
O: ["01110", "10001", "10001", "10001", "10001", "10001", "01110"],
P: ["11110", "10001", "10001", "11110", "10000", "10000", "10000"],
Q: ["01110", "10001", "10001", "10001", "10101", "10010", "01101"],
R: ["11110", "10001", "10001", "11110", "10100", "10010", "10001"],
S: ["01111", "10000", "10000", "01110", "00001", "00001", "11110"],
T: ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
U: ["10001", "10001", "10001", "10001", "10001", "10001", "01110"],
V: ["10001", "10001", "10001", "10001", "10001", "01010", "00100"],
W: ["10001", "10001", "10001", "10101", "10101", "10101", "01010"],
X: ["10001", "10001", "01010", "00100", "01010", "10001", "10001"],
Y: ["10001", "10001", "01010", "00100", "00100", "00100", "00100"],
Z: ["11111", "00001", "00010", "00100", "01000", "10000", "11111"],
}

const litPixelsFor = (letter: string) => {
const rows = glyphs[letter.toUpperCase()]
if (!rows) {
throw new Error(`LedLetter only supports capital A-Z, got "${letter}"`)
}

return rows.flatMap((row, rowIndex) =>
[...row].flatMap((pixel, columnIndex) =>
pixel === "1" ? [{ row: rowIndex, column: columnIndex }] : [],
),
)
}

export const LedLetter = ({
letter,
power,
gnd,
x = 0,
y = 0,
pitch = 2.5,
ledFootprint = "0603",
resistorFootprint = "0402",
resistance = "1k",
}: LedLetterProps) => {
const pixels = litPixelsFor(letter)
const startX = x - ((5 - 1) * pitch) / 2
const startY = y + ((7 - 1) * pitch) / 2

return (
<group>
{pixels.map(({ row, column }, index) => {
const ledName = `LED_${letter}_${index + 1}`
const resistorName = `R_${letter}_${index + 1}`
const pcbX = startX + column * pitch
const pcbY = startY - row * pitch
const schX = column * 2
const schY = -row * 1.6

return (
<group key={`${row}-${column}`}>
<resistor
name={resistorName}
resistance={resistance}
footprint={resistorFootprint}
pcbX={pcbX - 0.8}
pcbY={pcbY}
schX={schX - 0.8}
schY={schY}
/>
<led
name={ledName}
color="red"
footprint={ledFootprint}
pcbX={pcbX + 0.8}
pcbY={pcbY}
schX={schX + 0.8}
schY={schY}
/>
<trace from={power} to={`.${resistorName} > .pin1`} />
<trace from={`.${resistorName} > .pin2`} to={`.${ledName} > .pos`} />
<trace from={`.${ledName} > .neg`} to={gnd} />
</group>
)
})}
</group>
)
}

export default () => (
<board width="32mm" height="44mm">
<net name="V3_3" isForPower />
<net name="GND" isGround />
<LedLetter letter="A" power="net.V3_3" gnd="net.GND" />
</board>
)
PCB Circuit Preview

Usage

Use LedLetter inside any board and pass the nets that should power each LED string:

<LedLetter letter="A" power="net.V3_3" gnd="net.GND" />

The example uses one resistor for each LED. That is conservative for a small display because every pixel has its own current limiter. If you decide to wire LEDs in series or multiplex a larger display, calculate the resistor values and drive circuitry for the selected LED forward voltage, supply voltage, duty cycle, and desired current.

How the layout works

Each glyph is stored as seven strings with five characters per row. A 1 means that pixel is lit; a 0 means it is empty. litPixelsFor() converts the glyph into { row, column } coordinates, and LedLetter turns those coordinates into component positions:

  • pcbX and pcbY use pitch to form the physical LED grid.
  • schX and schY spread the LED-resistor strings into a readable schematic.
  • Component names include the selected letter and pixel index so every LED and resistor has a stable, unique name.
  • ledFootprint, resistorFootprint, and resistance are props, so the same component can switch between 0402 and 0603 assemblies.

Try another letter

Change the prop from letter="A" to any capital letter through letter="Z". The glyph table above includes all A-Z letters, so the same component can render letters for names, badges, indicators, or simple LED art.