A scalable IR transmission platform with dual implementations: 24-channel parallel FPGA-based transmitter (PYNQ-Z2) and Arduino-only alternative. Decode and test IR signals using dual Arduino Uno receivers with NEC-inspired protocol support.
- Project Overview
- System Architecture
- Transmitter Circuitry
- Project Structure
- Hardware Setup
- Software Setup & Installation
- Usage Guide
- IR Protocol Specification
- Customization & Development
- Troubleshooting
- Appendix
This project implements a high-capacity parallel IR transmission system designed for:
- Research & Testing: Transmit multiple IR signals simultaneously to evaluate receiver performance
- Device Control: Send NEC-compatible IR commands to multiple devices in parallel
- System Integration: Flexible architecture supporting both FPGA and Arduino implementations
Key Features:
- ✅ 24 parallel IR transmitter channels (FPGA version) — TESTED & WORKING
- ✅ Simultaneous multi-channel transmission with independent control
- ✅ Dual Arduino receiver validation setup
- ✅ NEC IR protocol support with custom extensions
- ✅ Low-cost Arduino-only fallback implementation
- ✅ Real-time signal monitoring and debugging
⚠️ Status: FPGA IR receiver (PL design) is NOT YET TESTED. Transmitter functionality is stable. Use external Arduino receivers for validation.
| Aspect | FPGA Version | Arduino Version |
|---|---|---|
| Transmitter | PYNQ-Z2 FPGA (24 channels) | Arduino Uno (1 channel) |
| Receivers | 2× Arduino Uno or external receiver | 1× Arduino + external receiver |
| Complexity | Advanced (Vivado design) | Simple (single sketch) |
| Cost | Higher | Lower |
| Scalability | 24 parallel channels | Single sequential channel |
| Use Case | Bulk testing, research | Hobbyist, simple control |
PYNQ-Z2 Board
├─ Processing System (PS) — ✅ TESTED
│ └─ Python/Jupyter Controller (nec_onlyFPGA.ipynb)
├─ Programmable Logic (PL)
│ ├─ NEC_IR_Receiver IP cores (×2) — ⚠️ NOT YET TESTED
│ ├─ IR_Transmission IP cores (×24) — ✅ TESTED
│ └─ AXI GPIO interfaces
└─ Physical I/O
├─ 24× IR LED outputs (AR0-AR13, A0-A5, PMODA) — ✅ TESTED
└─ 2× status/control pins
Receiver Setup (External - Recommended)
├─ Arduino Uno (Blue) → Pin 11 (Receiver 1) — ✅ TESTED
└─ Arduino Uno (Green) → Pin 10 (Receiver 2) — ✅ TESTED
Parallel_IR_Transmitters/
│
├── 📄 README.md # This file
├── 📄 IR_research.pdf # research that I conduct now
|── 📄 main.tex #The LaTeX file of my Research
│
├── 📂 PS\ part/ # FPGA Processing System (PS) - Python Side
│ ├── 📓 nec_onlyFPGA.ipynb # Jupyter notebook for PYNQ-Z2
│ │ # - NEC_IR_Controller_24ch class
│ │ # - Command transmission logic
│ │ # - Real-time control interface
│ │
│ ├── 📂 xilinx/overlays/own/
│ │ ├── design_1_wrapper.bit # FPGA bitstream file
│ │ └── design_1_wrapper.hwh # Hardware handoff file
│ │
│ └── 📂 arduino\ part/
│ ├── 📂 IR_TransmitterReceiver/ # Receiver sketch (standard)
│ │ └── IR_TransmitterReceiver.ino
│ └── 📂 IR_Receiver2/ # Receiver sketch (variant)
│ └── IR_Receiver2.ino
| └── 📂 Voltmetr/ # Receiver sketch (variant)
│ └── Voltmetr.ino
| └── Board_Voltmetr.txt
│
├── 📂 FPGA_Part/ # FPGA Programmable Logic (PL) - Vivado Design
│ ├── 📂 Own_IR_receiver/ # Vivado project (receiver reference) ⚠️ NOT TESTED
│ │ ├── Own_IR_Receiver.xpr
│ │ ├── design_1_wrapper.bit
│ │ └── [Vivado build artifacts]
│ │
│ └── 📂 Own_IR_transmitter/ # Vivado project (24-channel transmitter) ✅ TESTED
│ ├── Own_IR_transmitter.xpr
│ ├── design_1_wrapper.xsa
│ └── [Vivado build artifacts]
│
├── 📂 external_design/ # Circuit Design Files
│ ├── External_circuit.asc # 24 VCC GND port (SPICE)
│ ├── Safe_Driving_Circuit.asc # Protected driver circuit (SPICE)
│ ├── Driving_Ciruit.asc # Alternative driver (SPICE)
│ └── 📂 Altium/ # PCB Design (Altium Designer)
│ ├── 📂 24_external/ # 24-channel external driver PCB
│ │ ├── PcbLib1.PcbLib
│ │ ├── PCB_Project/
│ │ └── __Previews/
│ └── 📂 Driving_Circuit/ # Driving circuit PCB
│ └── Control_Circuit_safe/
│
├── 📂 No_FPGA_approach/ # Arduino-Only Alternative
│ └── 📂 IR_transmitter/
│ └── IR_transmitter.ino # Standalone Arduino TX sketch
│
├── 📂 Metasurface\ research_papers/ # Research Reading Materials
│ ├── My\ list.txt # Curated reading list
│ └── 📂 Step0/ # Research progress notes
│ └── Step\ to\ take\ here.txt
│
└── 📂 img/ # Hardware photos & diagrams
├── WhatsApp\ Image\ 2026-03-09\ at\ 17.52.39.jpeg
├── image-1.png
├── image-2.png
├── image-3.png
└── Image_for_more.png
The external_design/Altium/ folder contains complete PCB designs created with Altium Designer:
- Directory:
external_design/Altium/24_external/ - Purpose: Dedicated driver PCB for all 24 parallel IR LED transmitters
- Features:
- 24 independent driver channels with current limiting
- Thermal management and RF shielding considerations
- Integration connectors for FPGA board
- PCB Library files and design projects included
- Directory:
external_design/Altium/Driving_Circuit/ - Purpose: Individual IR LED driver circuit design
- Features:
- Optimized for single or multi-channel configurations
- Safe switching circuitry with transient suppression
- References corresponding SPICE simulations in
external_design/
Complementary SPICE circuit files for simulation and verification:
External_circuit.asc— 24-port external interface with VCC/GND distributionSafe_Driving_Circuit.asc— Protected driver with feedback and current monitoring
Recommendation: Review SPICE simulations before PCB manufacturing to validate signal integrity and power delivery.
The current hardware work uses two closely related IR transmitter circuits. Both use the same AO3400A low-side MOSFET driver and are controlled by the PYNQ-Z2 FPGA's 3.3 V NEC-modulated output. The IR LEDs are powered from a separate regulated 5 V supply, with the FPGA ground and 5 V supply ground connected together.
This is the higher-output transmitter configuration used in the main design. Two TSAL6200 IR emitters are connected in series and switched by one AO3400A MOSFET.
+5 V
|
TSAL6200
|
TSAL6200
|
30 Ω / 0.5 W
|
Drain
AO3400A
Source
|
GND
FPGA GPIO ── 330 Ω ── Gate
|
10 kΩ
|
GND
With a typical LED forward voltage of approximately 1.35 V per TSAL6200,
I ≈ (5 V - 2×1.35 V) / 30 Ω
≈ 76.7 mA
so both LEDs carry approximately 77 mA while the MOSFET is ON.
An optional second identical branch can be added if more optical power is required. Each parallel branch must have its own 30 Ω current-limiting resistor.
A second test configuration uses only one TSAL6200. The MOSFET driver remains the same, but the LED resistor must be increased because there is only one LED forward-voltage drop.
+5 V
|
TSAL6200
|
47 Ω / 0.5 W
|
Drain
AO3400A
Source
|
GND
FPGA GPIO ── 330 Ω ── Gate
|
10 kΩ
|
GND
The approximate current is
I ≈ (5 V - 1.35 V) / 47 Ω
≈ 77.7 mA
so the single LED also operates at approximately 78 mA.
The old 30 Ω resistor must not be used with only one LED, because the estimated current would become approximately
I ≈ (5 V - 1.35 V) / 30 Ω
≈ 122 mA
which is above the intended operating current.
| Component | Value / Part | Purpose |
|---|---|---|
| IR emitter | TSAL6200, 940 nm | Directional IR transmission |
| MOSFET | AO3400A | Low-side LED switching |
| Gate resistor | 330 Ω, 1/4 W | Limits FPGA gate charging current |
| Gate pull-down | 10 kΩ, 1/4 W | Keeps MOSFET OFF during startup/reset |
| Bulk capacitor | 100 µF, ≥10 V | Local energy storage |
| Decoupling capacitor | 1 µF ceramic | Supply decoupling |
| High-frequency capacitor | 100 nF ceramic | High-frequency decoupling |
| LED supply | Regulated 5 V | Supplies LED current |
| Control input | 3.3 V FPGA logic | NEC-modulated 38 kHz switching signal |
The single-LED circuit is useful as a minimum-power test configuration. If one emitter already gives reliable reception at the required receiver positions, it may reduce unnecessary optical spill into neighboring receiver regions. It does not completely remove interference from other independent transmitters.
Located in Metasurface\ research_papers/ folder:
- My list.txt — Curated list of important research papers and technical references related to the project
- Step0/ — Research progress notes and next steps for metasurface and IR transmission investigations
- IR transmission physics and optimization
- Metasurface applications for RF/IR engineering
- Signal processing and modulation techniques
- High-speed parallel data transmission
📖 Note: These materials provide theoretical background and advanced optimization strategies for future enhancements.
| Component | Quantity | Purpose |
|---|---|---|
| PYNQ-Z2 FPGA Board | 1 | Main controller (24-channel TX) |
| Arduino Uno | 2 | IR signal receivers + validation |
| IR LED Emitter | 24 | Transmitter elements (FPGA outputs) |
| IR Receiver Module | 2 | Signal decoders (1 per Arduino) |
| Driver Circuit | 24 | LED current limiting & switching |
| Copper sheet / barrier | 1 | RF isolation between TX channels |
| Jumper wires | ~100 | Interconnections |
| USB cables | 3 | PYNQ power, Arduino programming |
| Ethernet cable | 1 | PYNQ network (optional but recommended) |
| Component | Quantity | Purpose |
|---|---|---|
| Arduino Uno | 1 | IR transmitter controller |
| IR LED Emitter | 1 | Transmitter element |
| Driver Circuit | 1 | LED current limiting |
| Jumper wires | ~60 | Interconnections |
| USB cable | 1 | Power & programming |
| Channel | PYNQ-Z2 Pin | Driver Output |
|---|---|---|
| 1-8 | AR0-AR7 | IR LED Array 1 |
| 9-13 | AR8-AR12 | IR LED Array 2 |
| 14-19 | A0-A5 | IR LED Array 3 |
| 20-24 | PMODA0-PMODA4 | IR LED Array 4 |
Each pin: 3.3V logic → Buffer/Driver → IR LED anode (cathode to GND through 100Ω resistor)
- ⚡ Micro-USB cable to computer or dedicated 5V supply
- 🌐 Gigabit Ethernet cable for robust network connection to Jupyter server
| Component | Pin |
|---|---|
| IR Receiver Data | GPIO 11 |
| IR Receiver VCC | 5V |
| IR Receiver GND | GND |
| Component | Pin |
|---|---|
| IR Receiver Data | GPIO 10 |
| IR Receiver VCC | 5V |
| IR Receiver GND | GND |
- USB cables to different COM ports
Click to expand hardware documentation
FPGA Board + 24 IR Transmitter Array:

Arduino 1 (Blue) with IR Receiver Module:

Arduino 2 (Green) with IR Receiver Module:

- PYNQ-Z2 board with latest firmware
- 2× Arduino Uno boards
- Computer with USB ports
- Python 3.6+ (for local scripts)
- Arduino IDE 1.8+ (for sketches)
- Connect PYNQ-Z2 via Micro-USB and Ethernet to your network
- Power on and wait 2-3 minutes for boot
- Find the board's IP address (default:
192.168.2.99) - Reference: PYNQ Setup Guide
- Open browser:
http://192.168.2.99:9090/ - Password:
xilinx - Create a working folder (e.g.,
IR_Project)
Upload to PYNQ Jupyter server:
| Local File | Server Destination |
|---|---|
PS\ part/nec_onlyFPGA.ipynb |
~/IR_Project/ |
PS\ part/xilinx/overlays/own/design_1_wrapper.bit |
~/IR_Project/xilinx/overlays/own/ |
PS\ part/xilinx/overlays/own/design_1_wrapper.hwh |
~/IR_Project/xilinx/overlays/own/ |
- Open Arduino IDE
- Install library: Arduino-IRremote (Sketch → Include Library → Manage Libraries)
- Load:
PS\ part/arduino\ part/IR_TransmitterReceiver/IR_TransmitterReceiver.ino - Select Tools → Board → Arduino Uno
- Connect Arduino 1, select its COM port, upload
- Repeat with Arduino 2 on a different COM port
- Open both Arduino Serial Monitors (9600 baud)
- Run Jupyter notebook test cell
- Should see "Overlay loaded" message
- Open Arduino IDE
- Install Arduino-IRremote library
- Load:
No_FPGA_approach/IR_transmitter/IR_transmitter.ino - Modify pin assignments if needed (currently D2)
- Upload to single Arduino Uno
- Connect IR LED via driver circuit to D2 pin
- Open Serial Monitor (9600 baud) to send commands
- On PYNQ server: Open
nec_onlyFPGA.ipynb - Run cell 1: Loads
NEC_IR_Controller_24chclass and initializes FPGA
Example 1: Send commands
# In notebook cell:
controller = NEC_IR_Controller_24ch(
ir = NEC_IR_Controller_24ch(
"/home/xilinx/jupyter_notebooks/xilinx/overlays/own/design_1_wrapper.bit",
tx_bases=tx_bases,
btn_base=btn_base
)
try:
start = datetime.now()
# Each tx needs address and a command string of equal length
tx_addresses = [0x00]*24
# Example addresses
cmd_strs = [
"-0001+", # Channel 0: 5
"-0013+", # Channel 1: 6
"-0106+", # Channel 2: 7
"-0008+", # Channel 3: 8
"-0009+", # Channel 4: 9
"-0010+", # Channel 5: 10
"-0011+", # Channel 6: 11
"-0012+", # Channel 7: 12
"-0013+", # Channel 8: 13
"-0014+", # Channel 9: 14
"-0015+", # Channel 10: 15
"-0016+", # Channel 11: 16
"-0017+", # Channel 12: 17
"-0018+", # Channel 13: 18
"-0019+", # Channel 14: 19
"-0020+", # Channel 15: 20
"-0021+", # Channel 16: 21
"-0022+", # Channel 17: 22
"-0023+", # Channel 18: 23
"-0024+", # Channel 19: 24
"-0025+", # Channel 20: 25
"-0026+", # Channel 21: 26
"-0027+", # Channel 22: 27
"-0028+", # Channel 23: 28
]
)
# Transmit on channels 1 and 2 simultaneously
controller.send_cmd(0, '1') # Transmitter 0, command '1'
controller.send_cmd(1, 'B') # Transmitter 1, command 'B'- Check Serial Monitor on each Arduino
- Output format:
Address: 0xXX, Command: 0xXX - Timing: Signals should arrive within 100ms
- Open Arduino Serial Monitor at 9600 baud
- Type a character and press Enter
- Predefined mappings (0-9, A-F) send NEC codes
- Received signals can be monitored with external receiver
Edit the Send_Code() function:
case 'X':
IrSender.sendNEC(0x12345678, 32);
break;| Parameter | Value | Notes |
|---|---|---|
| Standard | NEC-compatible | Simplified NEC implementation |
| Total Frame Bits | 32 bits | Address + Command + Parity |
| Address Bits | 8 bits | Device identifier |
| Command Bits | 8 bits | Action/button code |
| Carrier Frequency | ~38 kHz | Standard IR frequency |
| Modulation | PWM (50% duty) | On-off keying |
| Protocol Channels | 24 (parallel FPGA) | Independent TX channels |
┌─ 32 bits total ─┐
│ Address (8b) │ Command (8b) │ ~Address (8b) │ ~Command (8b) │
└────────────────┘
- Address Field: Device identifier (0x00-0xFF)
- Command Field: Action code (0x00-0xFF)
- Inverse Fields: Checksum/error detection
| Key | Code | Key | Code |
|---|---|---|---|
| 0 | 0x16 | 8 | 0x52 |
| 1 | 0x0C | 9 | 0x4A |
| 2 | 0x18 | A | 0xAA |
| 3 | 0x5E | B | 0xBB |
| 4 | 0x08 | C | 0xCC |
| 5 | 0x1C | D | 0xDD |
| 6 | 0x5A | E | 0xEE |
| 7 | 0x42 | F | 0xF1 |
- FPGA Transmitter (24 channels): All transmitter channels working as designed
- Arduino Receivers: Both receiver boards successfully decode NEC signals
- Python/Jupyter Interface: Notebook controller and MMIO communication verified
- Protocol Implementation: Standard NEC encoding/decoding functional
- FPGA Receiver (PL design): NEC_IR_Receiver IP cores in the FPGA bitstream have not been validated
- Reason: Focus was on transmission performance; receiver testing deferred
- Recommendation: Use external Arduino receivers for current applications
- Future: Will integrate FPGA receiver testing in next release
- FPGA receiver functionality cannot be guaranteed in production
- No loopback testing between FPGA TX and FPGA RX
- All receiver-side validation currently relies on Arduino Uno boards and external circuits
| Symptom | Cause | Solution |
|---|---|---|
| No Jupyter server response | Board not powered | Check USB power indicator LED |
| Connection timeout | Network misconfiguration | Ping board IP: ping 192.168.2.99 |
| Overlay failed to load | Bitstream path incorrect | Verify .bit and .hwh files exist in correct directory |
| MMIO initialization error | Wrong base address | Verify address map in Vivado design |
| Symptom | Cause | Solution |
|---|---|---|
| No IR signal | LED not powered | Check driver circuit continuity (multimeter) |
| Weak/short-range signal | Insufficient LED current | Reduce resistor value (100Ω → 50Ω) |
| No receiver output | Receiver placement | Point LED directly at receiver (within 1 meter) |
| Garbled data | Baud rate mismatch | Set both Arduino + Serial Monitor to 9600 baud |
| Channel interference | No RF isolation | Place copper barrier between TX LED arrays |
| Symptom | Cause | Solution |
|---|---|---|
| Sketch upload fails | Wrong COM port | Tools → Port → Select correct COM port |
| Serial Monitor shows garbage | Baud mismatch | Set Serial Monitor to 9600 baud |
| Library not found | IRremote not installed | Sketch → Include Library → Manage Libraries → Search "IRremote" → Install |
| Symptom | Cause | Solution |
|---|---|---|
| Slow transmission | Jupyter kernel lag | Restart kernel: Kernel → Restart |
| Dropped frames | High FPGA load | Increase delay between transmissions (NEC_HOLD_TIME) |
| Inconsistent reception | Weak carrier frequency | Verify crystal oscillator on FPGA board |
- PYNQ Official Documentation: https://pynq.readthedocs.io/
- Vivado Design Suite: https://www.xilinx.com/products/design-tools/vivado.html
- NEC IR Protocol: https://www.sbprojects.net/knowledge/ir/nec.php
- Arduino-IRremote Library: https://github.com/Arduino-IRremote/Arduino-IRremote
PCB Design Files:
external_design/Altium/24_external/— Complete 24-channel driver PCB (Altium Designer)external_design/Altium/Driving_Circuit/— Individual LED driver PCB design
SPICE Circuit Simulations:
external_design/External_circuit.asc— VCC GND distribution circuitexternal_design/Safe_Driving_Circuit.asc— Protected version with feedbackexternal_design/Driving_Ciruit.asc— Standard IR LED driver
Metasurface\ research_papers/My\ list.txt— Curated reading list for IR transmission and metasurface topicsMetasurface\ research_papers/Step0/— Research progress notes and investigation roadmap
- nec_onlyFPGA.ipynb: Main control interface for 24-channel transmission
- IR_research.pdf: Research in a topic
- Own_IR_transmitter.xpr: Vivado project (24-channel implementation)
- Own_IR_receiver.xpr: Vivado project (receiver reference design)
This project builds upon:
- PYNQ framework (Xilinx/University of Sydney)
- Arduino IDE & libraries (Arduino LLC)
- NEC IR protocol (reverse-engineered standard)
Last Updated: June 2026
Status: Transmitter Stable | Receiver Under Development | PCB Design Complete


