TV Remote with CircuitPython

Build your own IR remote using QT Py RP2040 and CircuitPython! The LED arcade button can be programmed to transmit just about any IR signal.

NeoPixel Status

The LED illuminates when the arcade button is pressed and the QT Py's on-board NeoPixel LED turns on, letting you know the IR signal has been sent. The NeoPixel color switches from green to red.

Embedded Electronics

All of the electronics are housed inside a snap fit case. The QT Py RP2040 is secured to a quarter-sized Perma-Proto via headers making it modular.

Prerequisite Guides

Take a moment to browse through the following guides

3 x JST PH 2-Pin Cable
Male Header 200mm
3 x JST PH 2-Pin Cable
Female Connector 100mm
1 x Through-Hole Resistors
100 ohm 5% 1/4W - Pack of 25
1 x Silicone Cover Stranded-Core Ribbon Cable
10 Wire 1 Meter Long - 28AWG Black
1 x M3 Hardware Kit
Black Nylon Machine Screw and Stand-off Set – M3 Thread
1 x Short Header Female
Short header for ItsyBitsy (repurpose)
1 x Short Header Male
Short header for Feather (repurpose)

CAD Assembly

The owl is secured to the case top cover with hardware screws and nuts. The arcade button is panel mounted to the top of the Owl. The IR LEDs are press fitted into the Owl's eyes. The case framing snap fits onto the top and bottom covers. The QT Py is secured to the Perma Proto via headers. The Perma Proto PCB is secured to the bottom cover with hardware screws. The IR receiver is press fit into the case framing. 

CAD Parts List

STL files for 3D printing are oriented to print "as-is" on FDM style machines. Parts are designed to 3D print without any support material. Original design source may be downloaded using the links below:

  • Owl.stl
  • Owl-Case-Frame.stl
  • Owl-Case-Bottom.stl
  • Owl-Case-Top.stl
  • Owl-Plug-B.stl
  • Owl-Plug-A.stl

Build Volume

The parts require a 3D printer with a minimum build volume.

  • 95mm (X) x 95mm (Y) x 114mm (Z)

Design Source Files

The project assembly was designed in Fusion 360. This can be downloaded in different formats like STEP, STL and more. Electronic components like Adafruit's board, displays, connectors and more can be downloaded from the Adafruit CAD parts GitHub Repo.

The diagram below provides a visual reference for wiring of the components. This diagram was created using the software package Fritzing.

Adafruit Library for Fritzing

Use Adafruit's Fritzing parts library to create circuit diagrams for your projects. Download the library or just grab individual parts. Get the library and parts from GitHub - Adafruit Fritzing Parts.

Wired Connections

The QT Py RP2040 is soldered to the 1/4 sized perma-proto to make sharing grounds easier.

Button Swittch

  • Pin 1 to Pin A2 on QT Py RP2040
  • Pin 2 to GND on Perma-Proto

Button LED

  • Anode to Pin A1 on QT Py RP2040
  • Cathode to GND on Perma-Proto

IR Receiver 

  • Anode to Pin A0 on QT Py RP2040
  • Cathode to GND on Perma-Proto

IR LEDs

The IR LED features a 100ohm resistor wired in-line with ground.

  • Anode to Pin TX on QT Py RP2040
  • Cathode to GND on Perma-Proto

CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.

CircuitPython Quickstart

Follow this step-by-step to quickly get CircuitPython running on your board.

Click the link above to download the latest CircuitPython UF2 file.

Save it wherever is convenient for you.

To enter the bootloader, hold down the BOOT/BOOTSEL button (highlighted in red above), and while continuing to hold it (don't let go!), press and release the reset button (highlighted in blue above). Continue to hold the BOOT/BOOTSEL button until the RPI-RP2 drive appears!

If the drive does not appear, release all the buttons, and then repeat the process above.

You can also start with your board unplugged from USB, press and hold the BOOTSEL button (highlighted in red above), continue to hold it while plugging it into USB, and wait for the drive to appear before releasing the button.

A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.

You will see a new disk drive appear called RPI-RP2.

 

Drag the adafruit_circuitpython_etc.uf2 file to RPI-RP2.

The RPI-RP2 drive will disappear and a new disk drive called CIRCUITPY will appear.

That's it, you're done! :)

Safe Mode

You want to edit your code.py or modify the files on your CIRCUITPY drive, but find that you can't. Perhaps your board has gotten into a state where CIRCUITPY is read-only. You may have turned off the CIRCUITPY drive altogether. Whatever the reason, safe mode can help.

Safe mode in CircuitPython does not run any user code on startup, and disables auto-reload. This means a few things. First, safe mode bypasses any code in boot.py (where you can set CIRCUITPY read-only or turn it off completely). Second, it does not run the code in code.py. And finally, it does not automatically soft-reload when data is written to the CIRCUITPY drive.

Therefore, whatever you may have done to put your board in a non-interactive state, safe mode gives you the opportunity to correct it without losing all of the data on the CIRCUITPY drive.

Entering Safe Mode

To enter safe mode when using CircuitPython, plug in your board or hit reset (highlighted in red above). Immediately after the board starts up or resets, it waits 1000ms. On some boards, the onboard status LED (highlighted in green above) will blink yellow during that time. If you press reset during that 1000ms, the board will start up in safe mode. It can be difficult to react to the yellow LED, so you may want to think of it simply as a slow double click of the reset button. (Remember, a fast double click of reset enters the bootloader.)

In Safe Mode

If you successfully enter safe mode on CircuitPython, the LED will intermittently blink yellow three times.

If you connect to the serial console, you'll find the following message.

Auto-reload is off.
Running in safe mode! Not running saved code.

CircuitPython is in safe mode because you pressed the reset button during boot. Press again to exit safe mode.

Press any key to enter the REPL. Use CTRL-D to reload.

You can now edit the contents of the CIRCUITPY drive. Remember, your code will not run until you press the reset button, or unplug and plug in your board, to get out of safe mode.

Flash Resetting UF2

If your board ever gets into a really weird state and doesn't even show up as a disk drive when installing CircuitPython, try loading this 'nuke' UF2 which will do a 'deep clean' on your Flash Memory. You will lose all the files on the board, but at least you'll be able to revive it! After loading this UF2, follow the steps above to re-install CircuitPython.

Once you've finished setting up your QT Py RP2040 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.

To do this, click on the Download Project Bundle button in the window below. It will download as a zipped folder.

# SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT

import array
import pulseio
import board
from digitalio import DigitalInOut, Direction, Pull
from adafruit_debouncer import Debouncer
import neopixel

#  button setup with Debouncer
pin = DigitalInOut(board.A2)
pin.direction = Direction.INPUT
pin.pull = Pull.UP
button = Debouncer(pin)

#  button LED
led = DigitalInOut(board.A1)
led.direction = Direction.OUTPUT

#  onboard neopixel
pix = board.NEOPIXEL
num_pixels = 1
pixel = neopixel.NeoPixel(pix, num_pixels, brightness=0.8, auto_write=False)

#  PWM setup for IR LEDs
remote = pulseio.PulseOut(board.TX, frequency=38000, duty_cycle=2**15)
#  power on pulse array
# Prevent black from reformatting the arrays.
# fmt: off
power_on = array.array('H', [9027, 4490, 577, 563, 549, 1677, 579, 1674, 582, 558,
                             554, 559, 553, 561, 551, 562, 551, 1675, 580, 1674, 572,
                             567, 555, 1672, 573, 567, 556, 558, 554, 559, 553, 560,
                             552, 562, 550, 1675, 581, 560, 552, 561, 552, 561, 551,
                             563, 549, 1677, 579, 1674, 581, 560, 552, 561, 552, 1674,
                             581, 1673, 573, 1680, 575, 1679, 577, 563, 549, 565, 547,
                             1679, 577])
#  power off pulse array
power_off = array.array('H', [9028, 4491, 576, 563, 549, 1678, 578, 1701, 554, 533,
                              579, 561, 551, 562, 551, 536, 576, 1703, 552, 1700, 556,
                              558, 554, 1698, 547, 540, 582, 558, 554, 532, 580, 560,
                              552, 561, 552, 562, 550, 563, 549, 564, 548, 565, 547,
                              566, 546, 1707, 549, 1704, 551, 562, 550, 1703, 553, 1699,
                              556, 1697, 548, 1705, 551, 1701, 554, 560, 553, 560, 552,
                              1701, 554])
# fmt: on
#  array of the pulses
signals = [power_on, power_off]
#  neopixel colors
RED = (255, 0, 0)
GREEN = (0, 255, 0)
#  array of colors
colors = [GREEN, RED]
#  index variable
s = 0

while True:
    #  scan button for update
    button.update()
    #  if the button is pressed..
    if button.fell:
        #  send the pulse
        remote.send(signals[s])
        #  update onboard neopixel
        pixel.fill(colors[s])
        pixel.show()
        #  turn on button LED
        led.value = True
        #  advance the index variable
        s = (s + 1) % 2
    #  if the button is released..
    if button.rose:
        #  turn off the button LED
        led.value = False

Upload the Code and Libraries to the QT Py RP2040

After downloading the Project Bundle, plug your QT Py RP2040 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the QT Py RP2040's CIRCUITPY drive. 

  • lib folder
  • code.py

Your QT Py RP2040 CIRCUITPY drive should look like this after copying the lib folder and the code.py file.

CIRCUITPY
The default IR pulse arrays in the code will probably not work with your TV. You will need to decode your TV remote's IR pulse arrays and update the CircuitPython code.

Decode Your Pulses

Each remote command for your TV is stored in an array of IR pulses. Follow along with this guide page for the IR sensor to decode your remote's pulses. Once you decode the arrays that you want your owl to send from your TV remote, then you can update the CircuitPython code to work with your TV.

How the CircuitPython Code Works

The code stores two IR pulse arrays: one for powering on the TV and one for powering it off. These arrays are stored in the signals list for use in the loop. The colors array is setup with GREEN and RED NeoPixel colors. The onboard NeoPixel will change color depending on which IR command was sent.

You'll edit the power_on and power_off arrays with your decoded IR pulses so that the code will work with your TV.

remote = pulseio.PulseOut(pwm)
#  power on pulse array
power_on = array.array('H', [9027, 4490, 577, 563, 549, 1677, 579, 1674, 582, 558,
                             554, 559, 553, 561, 551, 562, 551, 1675, 580, 1674, 572,
                             567, 555, 1672, 573, 567, 556, 558, 554, 559, 553, 560,
                             552, 562, 550, 1675, 581, 560, 552, 561, 552, 561, 551,
                             563, 549, 1677, 579, 1674, 581, 560, 552, 561, 552, 1674,
                             581, 1673, 573, 1680, 575, 1679, 577, 563, 549, 565, 547,
                             1679, 577])
#  power off pulse array
power_off = array.array('H', [9028, 4491, 576, 563, 549, 1678, 578, 1701, 554, 533,
                              579, 561, 551, 562, 551, 536, 576, 1703, 552, 1700, 556,
                              558, 554, 1698, 547, 540, 582, 558, 554, 532, 580, 560,
                              552, 561, 552, 562, 550, 563, 549, 564, 548, 565, 547,
                              566, 546, 1707, 549, 1704, 551, 562, 550, 1703, 553, 1699,
                              556, 1697, 548, 1705, 551, 1701, 554, 560, 553, 560, 552,
                              1701, 554])
#  array of the pulses
signals = [power_on, power_off]
#  neopixel colors
RED = (255, 0, 0)
GREEN = (0, 255, 0)
#  array of colors
colors = [GREEN, RED]

The Loop

In the loop, s is used as the index variable and either has a value of 0 or 1. When the button is pressed, the IR LEDs alternate between sending out the power_on or power_off pulse arrays. The onboard NeoPixel is green when power_on has been sent and red when power_off has been sent.

#  if the button is pressed..
    if button.fell:
        #  send the pulse
        remote.send(signals[s])
        #  update onboard neopixel
        pixel.fill(colors[s])
        pixel.show()
        #  turn on button LED
        led.value = True
        #  advance the index variable
        s = (s + 1) % 2

Perma-Proto for QT Py

The QT Py RP2040 will connect to the quarter-sized Perma Proto using a mix of male and female headers. 

This makes the QT Py modular so it can be removed from the Circuit and repurposed.

Header Setup

Use the following headers to connect the QT Py to the Perma Proto.

  • 2x 1x7 short female header strip
  • 2x 1x7 short male header strip

Install QT Py to Perma Proto

Fit the two strips of male header pins under the QT Py PCB.

Place the two strips of female header pins under the male pins.

Position the QT Py in the center of the Perma Proto and place it down with the female pins going into the pins.

Solder Headers to Perma Proto

Carefully flip the two PCB's and begin to solder the pins in place.

Soldered QT Py and Perma Proto

Take a moment to check all of the pins have been properly soldered.

Wires for IR LEDs

Use the following wires for connecting the IR LEDs to the QT Py.

  • 1x 2-pin JST cable (female)
  • 1x 100ohm resistor
  • 4x Wires

Trim Leads

Take a moment to trim the leads from the IR LEDs and resistor short.

Solder First IR LED

Solder two of the single wires to the pins on one of the IR LEDs.

Use pieces of heat shrink tubing to insulate the leads.

Solder Second IR LED

Two wires will be soldered to the anode pin on the second IR LED.

Connect the a new single wire AND the anode wire from the first IR LED to the anode pin on the second IR LED.

Wire Second IR LED

Two wires will be soldered to the cathode pin on the second IR LED.

Connect the a new single wire AND the cathode wire from the first IR LED to the cathode pin on the second IR LED.

Wired IR LEDs

Take a moment to check the wires have been properly soldered to the IR LEDs.

Solder Resistor

Connect the 100ohm resistor to the cathode wire from the two IR LEDs. 

JST Cable for IR LEDs

Use the 2-pin JST female cable to connect the IR LEDs to the QT Py. 

Ground Wire

Solder the 100ohm resistor to the black wire on the JST cable.

Signal Wire

Connect the remaining anode wire to the red wire on the JST cable.

Finished IR LEDs

Take a moment to check all of the wires have been properly soldered to the two IR LEDs.

Cables for LED Button

Use 2x 2-pin JST cables to connect the LED button to the QT Py.

The stock wire lengths are suffice and do not need to be shortened.

Pins on LED Button

Use the photo to reference which pins are used for the LED and switch.

The leads for the LED are just outside the gray body of the switch.

The leads for the switch are inside the gray body of the switch.

Solder Cable to LED Pins

Connect the red wire to the positive pin on the LED.

Connect the black wire to the negative pin on the LED.

Solder Cable to Switch Pins

The polarity of the switch doesn't matter here so you can solder the wires to either pins.

Here we're keeping the wiring consistent with the LED.

Wired LED Button

Take a moment to check the wires are properly soldered.

Cable for IR Receiver

Use a 3-wire ribbon cable to connect the IR receiver to the Perma Proto.

Measure and cut the ribbon cable so it's about 3in (76mm) in length.

Solder Cable to IR Receiver

Trim the leads short before soldering the wires. 

Solder the three wires from the cable to the three pins on the IR Receiver.

Wired IR Receiver

Double check the three wires have been properly soldered to the pins on the IR Receiver.

Cables for Perma Proto

Use the following cables to make connecting the Perma Proto to the components.

  • 3x 2-pin JST cables
  • 1x 3-wire ribbon cable

Ground and Power Rails

Use a 2-wire cable to connect the ground and 3V pins to one of the power and ground rails on the Perma Proto.

Solder a wire from the GND pin on the QT Py to one of the GND rails on the Perma Proto.

Solder a wire from the 3V pin on the QT Py to one of the power rails on the Perma Proto.

 

Solder IR Receiver 

Connect the wires from the IR Receiver to the following pins the Perma Proto.

  • Pin 1 from IR Receiver to A0 Pin
  • Pin 2 from IR Receiver to GND pin
  • Pin 3 from IR Receiver to 3V pin

Solder Cable for Button LED

Connect the two wires from one of the 2-pin JST cables to the following pins

  • Black wire to GND pin on Perma Proto
  • Red wire to A1 pin on Perma Proto

Solder Cable for Button Switch

Connect the two wires from another 2-pin JST cable to the following pins.

  • Black wire to GND pin on Perma Proto
  • Red wire to A2 pin on Perma Proto

Solder Cable for IR LEDs

Connect the two wires from the last 2-pin JST cable to the following pins.

  • Black wire to GND pin on Perma Proto
  • Red wire to TX pin on Perma Proto

Wired Perma Proto

Double check all of the wires have been properly soldered.

Hardware for Owl

Use the following hardware for attaching the Owl to the top cover of the enclosure.

  • 4x M3 x 6mm
  • 4x M3 nuts

Top Cover

Orient the top cover of the enclosure so it matches the photo. The side with the longer edge is oriented with the front of the owl.

Secure Owl to Cover

Use the M3 hex nuts to secure the Owl to the top cover.

Use the large opening on the back of the owl to get a better hold of the hex nuts. 

Holder the hex nut in place while fastening the screws through the bottom of the cover.

Secured Owl

Take a moment to check the orientation of the top cover is correct.

Install IR LEDs

Insert an IR LED into one of the Owls' eye to test fit.

If it's too tight, use a filing tool to loosen up the opening.

Press the two IR LEDs through the eyes inside of the Owl.

IR LED Eyes

Take a moment to check the IR LEDs have been properly installed into the Owl's eyes an ensure they're fully seated.

Install LED Button

Insert the two JST cables from the LED button through the top opening of the Owl's head.

Insert the hex nut that was included with the LED button through the back opening of the Owl with the cables threading through.

Secure LED Button

Push the LED button all the through the hole until it's been fully seated.

Twist to fasten the hex nut onto the threads of the LED button.

Fasten the hex nut until it's fully secured to the LED button.

Route Cables 

Grab hold of the three JST cables and pull them through the large opening on the top cover.

Bottom Rubber Feet

Take a moment to install the 4x rubber feet to the corners of the bottom cover.

Remove feet from pack by peeling off from the backing.

Feet feature adhesive backing so they can just be stuck onto each corner.

Install Perma Proto

Grab the Perma Proto PCB and place it over the bottom cover with the two mounting holes lined up.

Orient the PCB so the USB-C port from the QT Py lines up with the notch.

Insert and fasten 2x M3 x 4mm long screws through the two mounting holes on the Perma Proto PCB.

 

Install IR Receiver 

Press fit the IR receiver through the opening in the framing of the case.

Install Bottom Cover

Orient the bottom cover with the framing and press fit them together.

Connect JST Cables

Start connecting the cables from the Owl to the cables from the Perma Proto PCB.

Connect the cable from pin A0 to the IR LEDs.

Connect the cable from pin A1 to the Button's LED.

Connect the cables from pin A2 to the Button's switch.

Close Case

Begin closing the enclosure by bringing the top cover to the framing.

The top cover snap fits over the framing.

USB Opening

The back of the enclosure features an opening for a USB-C cable to provide power and reprogramming.

Additional Plug

You have the option to close the large opening on the back of Owl with the plug.

Use adhesives to secure the two halves together to form the plug.

The plug is press fitted onto the large opening on the back of the Owl. It will only fit correctly one way.

Connect USB-C

Insert and plug a USB-C cable through the opening on the back of the enclosure.

Test Circuit

Take a moment to test out the circuit by pressing the LED arcade button.

The IR LED's inside the Owl should be aimed towards the desired TV.

 

Final Build

Congratulations on building your IR TV Remove Owl!

This guide was first published on Jul 13, 2022. It was last updated on 2023-12-05 11:35:15 -0500.