Skip to the content
PCBKitNew board
2 layers62 × 117 mmUniversal remote, the printed circuit board in 3D

ESP32-based universal infrared remote control with touch screen. It can learn IR signals, save them to an SD card, resend them, and transmit built-in brand codes.

Size
62 × 117 mm
Copper layers
2
Thickness
1.6 mm
Parts
21
Nets
34
Pads
92
Tracks, vias
264, 20
Routed
Yes
KiCad
KiCad 8.0 (file 20240108)
Created
17 Oct 2024
Updated
27 Oct 2026

Overview

Universal remote is an open-source printed circuit board by paco7828, from the paco7828/universal-remote repository on GitHub. It is a 2-layer board of 62 × 117 mm with 21 parts on 34 nets, routed with 264 tracks and 20 vias.

Its parts include 7 resistors, 4 connectors and 1 IC. It belongs with the AVR (ATmega, ATtiny) boards of this library.

README

README.md

Universal Remote

ESP32-based universal infrared remote control with touch screen. It can learn IR signals, save them to an SD card, resend them, and transmit built-in brand codes. The entire UI is touch-controlled, featuring scrollable lists and a virtual keyboard.


Hardware

ComponentDescription
MCUESP32 (Using FSPI / SPI2 bus)
DisplayILI9341 2.4" TFT, 240x320, portrait (rotation 0)
TouchXPT2046 resistive touch, via SPI
StorageMicroSD card
IR TransmitterTSAL6200
IR ReceiverIRM-3638T

The display and the SD card use the same SPI bus (shared MOSI/MISO/CLK), multiplexed with CS lines. Before sending an IR signal, the code sets SD_CS and TOUCH_CS HIGH to avoid bus conflicts.

Pinout

SignalGPIO
TFT CS7
TFT RST10
TFT DC2
TFT MOSI6
TFT MISO5
TFT CLK4
Touch CS8
Touch IRQ9
SD CS3
IR TX0
IR RX1

Dependencies

  • LovyanGFX - display + touch driver (ILI9341 + XPT2046)
  • IRremoteESP8266 - IR receive / send / decode
  • Preferences - ESP32 NVS (for saving themes)
  • SD, SPI - Arduino ESP32 core built-in
  • ./IR-codes.h - custom header with built-in Pronto Hex codes (EPSON and NEC arrays). Without this, the project will not compile.

Features

Signal learning (Receive)

  1. Signal options -> Receive starts listening mode.
  2. The receiver decodes the signal into decode_results.
  3. The code accepts a maximum of 3 frames at once within a signal (MAX_FRAMES = 3), measuring the pause between them as gap_ms.
  4. Repeat frames and identical frames are discarded.
  5. An UNKNOWN (raw) signal is only accepted if it is the first frame, and contains at least 10 pulses, filtering out noise.
  6. The captured signal type is determined automatically:
    • SIG_VALUE - known protocol, fixed-length value (e.g., NEC)
    • SIG_STATE - AC-like protocols with byte-array states
    • SIG_RAW - unknown, raw microsecond array
  7. After capture, a QWERTY keyboard appears to name the signal (max 25 characters, letters + -).
  8. Saved to the SD card: /saved-signals/<group>/<name>.txt.

Signal sending (Transmit)

  • Saved signals: Signal options -> Transmit -> group list -> signal list -> Send. The group is the part of the signal's name before the - (e.g., TV-power -> TV group).
  • Built-in signals: Built-in signals -> brand -> signal -> Send.
  • Long pressing the Send button repeats the signal (except for SIG_STATE signals).
  • Transmission strategy:
    • SIG_VALUE: irsend.send(protocol, value, bits) - for RC5/RC6, the toggle bit switches automatically on each new press.
    • SIG_STATE: irsend.send(protocol, state, bytes).
    • SIG_RAW: irsend.sendRaw(rawData, len, khz).
    • If protocol sending returns false, but raw data is available, an automatic raw fallback occurs.
  • For multi-frame signals, there is a gap_ms pause between frames.

Built-in signals

The IRCode structures declared in IR-codes.h store the codes in Pronto Hex format. The firmware converts them to raw microsecond arrays using the prontoToRawSignal() function and calculates the carrier frequency with prontoCarrierKHz().

Currently, only these are available from the UI in the hardcodedBrands[] array:

  • EPSON
  • NEC

(Other brands will only appear if you add them to the array.)

SD Card Management

  • Info - total / free / used space + number of saved signal files.
  • Files - file browser with directory navigation, size display, and individual file deletion (Del button, !NO CONFIRMATION MESSAGE!).
  • Format - keeps the /System Volume Information and /built-in-signals directories, deletes everything else.
  • File list limit: 500 items.

UI / Themes

Three selectable color themes (persisted in NVS):

  • Futuristic Red (default)
  • Futuristic Green
  • Futuristic Purple

Theme switching is displayed with an animated spreading circle effect.

Other UI features:

  • Boot splash: UNIVERSAL / REMOTE text for ~2 seconds.
  • Scrollable lists with a sprite buffer (LGFX_Sprite) to prevent flickering.
  • Double tap on a list item = open.
  • Long hold on a repeatable button = rapid fire (REPEAT_INTERVAL = 200 ms).
  • Maximum number of buttons per screen: 30.
  • Button style: corner accents + scanline pattern.

Navigation Structure

text
Main Menu
├── Signal options
│   ├── Transmit  ->  [Group]  ->  [Signal list]  ->  Send
│   └── Receive   ->  Listening... ->  Captured!   ->  Keyboard -> Save
├── Built-in signals
│   └── [Brand]   ->  [Signal list] ->  Send
├── SD Card options
│   ├── Info
│   ├── Files  ->  browser (Up / Back / Del)
│   └── Format ->  Yes, format / Cancel formatting
└── Change theme
    ├── Futuristic Red
    ├── Futuristic Green
    └── Futuristic Purple

If SD initialization fails, "SD Card options" disappears from the main menu.


Saved signal file format

Not binary - each signal is a simple, line-separated .txt file:

text
# UniRemote IR signal
name: TV-power
carrier_khz: 38
frame: 1
gap_ms: 40
protocol: NEC
bits: 32
value: 0x20DF10EF
length: 68
raw: 9000,4500,560,560,...
frame: 2
...

Fields per frame:

  • gap_ms - pause before the next frame (only if there is a next frame)
  • protocol - name returned by typeToString()
  • bits - number of bits for SIG_VALUE, bytes×8 for SIG_STATE
  • value - hex value (SIG_VALUE)
  • bytes - space-separated hex bytes (SIG_STATE)
  • length + raw - raw microsecond array (always saved if available)

When loading the file, the code automatically determines the kind based on the existing fields: value -> state -> raw priority.

Folder structure:

text
/saved-signals/
├── TV/
│   ├── power.txt
│   └── mute.txt
├── Misc/
│   └── random-signal.txt

Names without - are automatically placed in the Misc group.


File structure

FileRole
UniversalRemote.inoFull firmware
IR-codes.hBuilt-in Pronto Hex codes (EPSON, NEC arrays, IRCode struct)

Parts

21
QtyValueFootprintReferences
61KResistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P5.08mm_HorizontalR2, R3, R4, R5, R6, R7
11.8TFT-SCREENConnector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_VerticalJ1
1220Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P5.08mm_HorizontalR220
1Arduino_Nano_v3.xModule:Arduino_NanoA1
1Battery chargerConnector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_VerticalJ2
1IR-RxOptoDevice:Vishay_MOLD-3PinU1
1IR-TxLED_THT:LED_D5.0mm_IRGreyIR-Tx
1Li-Po-3.7VConnector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_VerticalBT1
1Power switchConnector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_VerticalJ4
1Voltage boosterConnector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_VerticalJ3
1backBtnButton_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH2xxxxxxV2backBtn
1confirmBtnButton_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH2xxxxxxV2confirmBtn
1downBtnButton_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH2xxxxxxV2downBtn
1leftBtnButton_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH2xxxxxxV2leftBtn
1rightBtnButton_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH2xxxxxxV2rightBtn
1upBtnButton_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH2xxxxxxV2upBtn

Questions

How big is the Universal remote PCB?

It measures 62 × 117 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Universal remote use?

21 parts. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Universal remote KiCad files?

From the paco7828/universal-remote repository on GitHub, at the commit this page was made from: the board is v1/pcb-kicad/universal_remote.kicad_pcb and its schematic v1/pcb-kicad/universal_remote.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Universal remote design in my own project?

Its repository states no licence, so its author keeps all rights: ask paco7828 before reusing the design.

Similar boards

  • 2 layers28.3 × 39.8 mmLevelShifter, the printed circuit board in 3D

    LevelShifter

    gusmanb/logicanalyzer

    24 channel, 100Msps logic analyzer hardware and software

    21 parts5.1kGPL-3.0
  • 2 layers171.2 × 72.9 mmAmiga gfxcard before pour, the printed circuit board in 3D

    Amiga gfxcard before pour

    mntmn/amiga2000-gfxcard

    MNT VA2000, an Open Source Amiga 2/3/4000 Graphics Card (Zorro II/III), written in Verilog

    21 parts1k
  • 2 layers93.9 × 46 mmRectifier, the printed circuit board in 3D

    Rectifier

    rene-dev/stmbl

    AC Servo Driver for STM32F4

    21 parts990GPL-3.0
  • 2 layers45.5 × 42.9 mmEsphome han reader, the printed circuit board in 3D

    Esphome han reader

    hackclub/OnBoard

    💡 Join 1,000 teenagers and make your first PCB with a $100 grant!

    21 parts915
  • 2 layers31.8 × 14.5 mmEsp prog, the printed circuit board in 3D

    Esp prog

    drandyhaas/KiCadRoutingTools

    A set of Python/Rust tools to aid routing in KiCad

    21 parts744MIT
  • 2 layers23.5 × 15.8 mmQuiverAttachPCB, the printed circuit board in 3D

    QuiverAttachPCB

    Arrow-air/project-quiver

    Project Quiver documentation, designs, and project artifacts.

    21 parts617CERN-OHL-S-2.0
  • 2 layers69.9 × 41.3 mmHeaterMeterLCD, the printed circuit board in 3D

    HeaterMeterLCD

    CapnBry/HeaterMeter

    HeaterMeter and LinkMeter Arduino BBQ Controller

    21 parts557MIT
  • 2 layers161.3 × 111.8 mmDoj, the printed circuit board in 3D

    Doj

    kelu124/echomods

    Open source ultrasound processing modules and building blocks

    21 parts463