Infrared Remote Control With PIC12F1840

by pityukecske in Circuits > Microcontrollers

463 Views, 4 Favorites, 0 Comments

Infrared Remote Control With PIC12F1840

image1-9.png
image1-7.png

This instructable is for anyone who wants to add remote control capabilities to their PIC application. Even though this circuit is based on the PIC12F1840 microcontroller code can be easily ported to any other controller given that it has a CCP module available. Read through this instructable to find out how you can use the CCP module of the PIC to decode infrared remote control message and how you can control 64 LEDs with an 8-pin microcontroller!

Supplies

image1-1.png
image1-98.png
image1-57.png
FVE9BO8M23B80G9.png

The following tools and components are required if you intend to copy this small project (I raided my Arduino component kit for this one):

  1. 1 x PC or Laptop
  2. 1 x PICkit4 or similar PIC programmer
  3. 1 x Logic analyzer
  4. 1 x RC5-compatible remote control
  5. 1 x PIC12F1840 microcontroller
  6. 1 x VS1838B infrared receiver
  7. 3 x 10k resistor for pull-up/pull-down purposes
  8. 1 x 100R series resistor for VS1838B recommended by Vishay
  9. 2 x 74HC595 shift register
  10. 1 x ULN2803 low side driver
  11. 1 x 8x8 LED dot matrix
  12. 1 x breadboard
  13. 1 x breadboard tool (very useful)
  14. n x wires for breadboard
  15. Wire clipper, pliers

It helped a lot to have:

  1. Diagno, my logic probe for general debugging of signal levels
  2. Scanalogic logic analyzer for RC-5 tuning

RC-5 Infrared Communication Protocol

image1-61.png
RC5.png
F6OG6ORM23B7WZM.png

The RC5 is a simple but effective protocol that has been used in remote controls for many years. It's reliable and easy to implement, which is probably why it's so popular among hobbyists who wish to add a remote control to their projects. Let's see how the concept looks like looks like!

The infrared LED of an RC-5 remote control emits messages as a series of pulses modulated over a 36KHz (or close) carrier wave. This makes it easier for these signals to "bounce" off walls or objects and end up being picked up by a receiver. The receiver - just like the one I used - decodes the received pulse trains and returns the initial message on its output.

The period of the 36KHz modulation frequency is 27.777us and the duty cycle of the emitted square-wave is 25%. Every single bit of an RC-5 message consists of 32 carrier pulses and 32 carrier pulse worth of "silence" which means one bit-time is 32 * 2 * 27.777us = 1.778ms. There are 14 bits of data in a transmission resulting a 24.889ms message-time. If a button is held down, the entire message gets re-transmitted after 113.778ms. If the same button is pressed multiple times, a bit field called "toggle" is swapped with every message - this is how the protocol distinguishes between a single, continuous press and multiple, individual presses.

The PIC12F1840 Microcontroller

image1-0.png
image1-20.png

For those familiar with microcontrollers, the PIC12F1840 offers a robust platform for embedded applications. This 8-bit device boasts a rich feature set, making it a popular choice for a wide range of projects from both consumer and industrial applications. This time we will use it to decode the RC-5 message coming from the remote through the VS1838B IR decoder and to display messages on a dot matrix display.

Key Features:

  1. 16K words of flash program memory for storing application code.
  2. 512 bytes of RAM memory for data storage and variables.
  3. 10-bit analog-to-digital converter enables measurement of analog signals.
  4. Enhanced capture/compare/PWM module controls PWM signals and timing events.
  5. Timers allow accurate timing loops to be created
  6. External interrupt for catching events from the outside world
  7. USART module facilitates serial communication with other devices.
  8. I2C and SPI interfaces supports communication with various peripherals.
  9. more...

Microchip provides a useful set of development tools for the PIC12F1840, we will be using the following:

  1. MPLAB IDE - a development environment for writing, compiling, and debugging code.
  2. MPLAB XC8 Compiler - a compiler optimized for PIC microcontrollers
  3. MPLAB PICkit 4 - I used it for programming, debugging and powering the microcontroller

The controller is at the heart of this small demo project and the rest of the circuit builds around it. Looking at the circuit it is pretty surprising that the essence (remote control and display, that is) is controlled by only four pins. In terms of peripherals and free memory we can still pack a very large amount of features into the device - I am thinking of making a simple remote controller snake game next with this hardware (with its own PCB, though).

I attached the HEX file to this step, you can burn it into the PIC with any programmer that supports the device. I would attach the project as well, but the UI won't let me, if someone needs it just ask and I will e-mail it.

Other Integrated Circuits in the Circuit

image1-2.png
image1-79.png

I used two 74HC595 shift registers and an ULN2803 for the display control.

HC595 Shift Registers

HC595 shift registers are a logic devices often used as output expanders employed in applications that require controlling more outputs or devices than the main controller could directly handle. The HC595 has serial input and parallel output and allows easy cascading of multiple devices. This makes it ideal for driving LED matrices, LCD displays, and other devices that require a large number of controlled outputs. I have used two of them to index columns and drive rows on my dot matrix display.

ULN2803A Darlington Array

The ULN2803A is a high-current Darlington array, designed to drive high-current loads such as relays, solenoids, or LEDs. It consists of seven independent Darlington transistor pairs, each capable of sinking up to 500 mA of current. This makes it a suitable choice for applications where the output current requirements exceed the capabilities of standard logic gates; in my case, however, it got picked because of the compact way it packaged eight transistors - it saves a lot of breadboard space.

Breadboarding Everything Together

1000014099.jpg
1000014103.jpg
1000014105.jpg
1000014102.jpg
1000014104.jpg
1000014101.jpg
1000014100.jpg

My breadboards used to be a mess, now they are well organized. The difference between then and now is this tool: https://www.thingiverse.com/thing:3862775. Unbelievably simple but incredibly useful. I use LAPPKABEL 2x2x0.8 wires for my prototypes. I peel them out of the 4 wire harness and use one or more of the four available colors (red, black, yellow and white) to route everything.

This breadboard became a bit crowded near the display and I had to compact wires on top of each other - this is why the display is lifted with two headers.

The steps of creating a wire for breadboard use:

  1. strip about 6mm of wire of the chosen color
  2. use a plier to bend the stripped end to 90°
  3. insert this end into the breadboard tool to the distance your setup asks for
  4. cut the cable to have a 8mm tail after the edge of the breadboard tool
  5. use a clipper to strip the isolation exactly at the edge of the breadboard tool
  6. use the side of the plier to bend the newly stripped end down along the side of the breadboard tool

Once the wire is stripped and bent, push it into its designated place.

I came up with the setup you see after I decided to use a single breadboard. I basically lined everything up and left a bit of space between them for aesthetics (proved to be quite useful later during wiring). I connected the power lines first, then I wired the display anodes (rows) to one of the SN74HC595 registers then continued with adding the SN74HC595 - ULN2803A interconnections. Finally, I wired the cathodes (columns) to the ULN output. Having 8 transistors in one package (like the ULN has) was a very good idea, I wouldn't have been able to fit 8 separate NPN-s on there.

Once the display driving side was completed, I cascaded the and connected the chain to the microcontroller. I used the most conveniently placed pins as the only hardware constraint I had was to preserve the RA2 GPIO for the infrared receiver. This is a must, because this is the GPIO that has the CCP and external interrupt capability multiplexed to it.

With this done I added some biasing resistors where it was needed and added the reset button and infrared receiver sensor + bent some wires to have them connected to the PIC.

The PICkit was wired with Dupont wires, no need for straps here as it connects directly to the breadboard.

A Little Bit of PIC Peripheral Insight

image1-46.png

I am using two (three if we count the timer associated with CCP1) important peripherals of the PIC to make everything work, these are he following:

External interrupt

External interrupts are basically a trigger for actions, a mechanism used in microcontrollers to respond to events occurring outside the device. These events can be triggered by various sources, such as:

  1. push buttons - pressing a button can generate an interrupt
  2. communication interfaces - data received on serial ports or other interfaces can initiate interrupts
  3. sensors - changes in sensor output values can trigger interrupts

What happens when an external interrupt occurs:

  1. interrupt is requested, the interrupt source sends a signal to the microcontroller's interrupt controller to suspend normal code execution and take care of, or "handle" the interrupt
  2. interrupt handling is done with the microcontroller suspending its current tasks and jumping to the predefined interrupt service routine (ISR)
  3. the ISR performs the necessary actions to handle the interrupt, in our case it will arm the compare module with a number that will give a CCP interrupt after around 300ms. Once the handling is done the ISR returns and normal code flow is resumed from where the MCU left off.

One would use interrupts whenever quick reaction to an event is needed. If there is a lengthy display update procedure in the code simply polling a GPIO with an "if" statement would mean large delays in event handling. Interrupt service routines provide improved responsiveness.

Timer and Compare

The compare module is a versatile timing feature found in many microcontrollers, including the PIC12F1840. It's a versatile tool that allows you to compare a timer value with a predefined reference value. This comparison can be used to trigger various actions, such as PWM generation, time measurement or event triggering like Initiating specific actions based on timing conditions.

  1. timer module must be configured to count up from zero to a specific starting value. Stepping speed must also be set through the pre-scaler bits of the timer configuration register
  2. reference value must be set (CCPR1L registers) that the timer will compare against
  3. the microcontroller hardware continuously compares the current timer value with the reference value
  4. when the timer value matches or exceeds the reference value, an interrupt or flag is generated, signaling that the comparison condition has been met

The compare module is a valuable tool for a wide range of applications, providing precise timing control and flexibility, I used it before for SG0 servo motor signal generation, square wave generation and for HCSR-04 trigger impulse generation. If you understands its functionality and features, it will become a very useful embedded system tool in your belt.

Firmware for the RC-5

image1-41.png
Peripheral.png
scanastudio.png

The core of RC-5 signal capture involves using the RA2 GPIO pin to detect incoming infrared pulses. This pin serves a dual purpose: as an external interrupt source and as the input for the RC-5 data itself. We need to invert the captured input value to accurately decode the data as a "1" bit is represented by a 0->1 transition in this protocol and we are sampling in the first half of the bit-time.

Capture and Processing Sequence

  1. external interrupt configuration: the external interrupt is configured to trigger on a falling edge on the RA2 pin, which marks the beginning of an RC-5 frame. The following happens:
  2. further external interrupts are disabled to prevent multiple interrupts from occurring during a single RC-5 frame
  3. time-based interrupts are enabled, a time-based interrupt (that of the CCP module) is armed to occur approximately 300 milliseconds after the ISR is executed. This ensures that the next, CCP interrupt aligns with the start of the first RC-5 bit
  4. on the next ISR handling the CCPR1L comparison register is adjusted to trigger the next interrupt after exactly one bit time
  5. the current value of RA2 is shifted into a buffer, capturing the RC-5 data bit
  6. a bit counter is incremented to keep track of the number of captured bits.
  7. after 13 bits have been captured, the ISR disables further time-based interrupts, re-enables external interrupts, clears the counter, and signals the main loop that a complete RC-5 message has been received.
  8. the main loop decodes the received RC-5 message to determine the corresponding button press and displays the appropriate number.

Important notes

  1. the RA2 pin serves as both an interrupt source and an input for the RC-5 data.
  2. Manchester encoding is used to represent data bits, inverted value must be saved
  3. time-based interrupts are used to accurately capture RC-5 bits
  4. a counter tracks the number of captured bits to determine frame completion
  5. the main loop processes the decoded RC-5 message and performs the desired action

Tuning

Since the documentation wasn't or was poorly available for many manufacturers, there are slight differences in the remote controls' carrier frequency which brings the entire chain down with it. I ended up using the one GPIO I had left for tuning this part of the firmware. I toggled an LED on the free GPIO whenever a CCP interrupt occurred and had a look with my very old Scanalogic logic analyzer to figure out just how long the bit times are and how good the sync between the two signals is. We want this LED-toggle to fall more or less on the same "place" in time within a bit-time, and we want this for every bit within the RC-5 frame. After some adjustments I managed to pin down the value I needed to write in the CCP comparison registers to match the bit time accurately.

I attached a capture made with the logic analyzer that shows this sequence: 11 0 101000 00101

The frame begins with two start bits, a toggle bit, 5 address bits and 6 command bits. I pressed "5" to get this pattern on the VS1838B output and the LED. I am only decoding the command section of the remote, since the others (apart of the toggle bit) won't change withing the same remote controller.

Concept and Firmware for the Display

image1-5.png
image1-76.png
digit5.png

Persistence of vision

Persistence of vision is a phenomenon where the human eye retains an image for a short period of time even after it's no longer present. This allows us to perceive continuous motion from a series of still images displayed in a rapid sequence.

In a dot matrix LED display, each LED represents a pixel. By rapidly turning on and off individual pixels in a specific sequence, we can create the illusion of a number that is simply there, even though in the background the rows/columns are scanned through one after the other. Having the LEDs run sequentially instead of having them ON at the same time is what allows the circuit to be powered by the power-wise weak PICkit.

I created three look-up tables to figure out what LEDs need to be turned on for a certain letter or number to display. I also intend to create a running text animation (and snake) on this controller, there is still plenty of flash and RAM for that and it would be a good educational reference. The loop would have to be timer based to provide a steady frame rate - right now I simply call it in the main loop which is luckily fast enough to hide the scanning nature of the display drive. By understanding persistence of vision and its application in dot matrix LED displays, you can create engaging and visually appealing animations and effects.

To be able to display different rows with the same shift register you must have the content for each row readily available in an array. Then, you can push a pattern out and index the first column. After that you can move to the second byte of that character bitmap and address the second column. When the time is right you move to the third byte from the look-up table and address the third column. If you do this for all columns and you do it fast enough, the patterns will look like they are being displayed at the same time. One register scans through the bytes, the other through the columns.

Once you got a column write going, you can add all sort of fancy stuff like additional characters or icons, or scrolling text, or anything you have the patience to implement.

Firmware breakdown

Here is a short snippet from the source files, this is the part that loads the bytes into the HC595 shift registers:

// Function to shift data into the shift registers and latch it
void HC595_ShiftBytes( uint8_t dataL, uint8_t dataH ) {
uint8_t i;

for (i = 0; i < 8; i++)
{
HC595_SER_LAT = (dataL >> i) & 1;
HC595_SRCLK_LAT = 1;
HC595_SRCLK_LAT = 0;
}
for (i = 0; i < 8; i++)
{
HC595_SER_LAT = (dataH >> i) & 1;
HC595_SRCLK_LAT = 1;
HC595_SRCLK_LAT = 0;
}

HC595_OE_RCLK_LAT = 1;
HC595_OE_RCLK_LAT = 0;
}


This is from the header file:

0x00, 0x00, 0x00, 0x27, 0x45, 0x45, 0x45, 0x39, // 35 5


As you can see, there are two bytes shifted out, first is the column byte (cathodes driven through the ULN2803) second is the row byte (anodes driven by the second SN74HC595). Columns are simply turned on one by one, this is why you can see the 0x10...0x08...0x04...0x02...0x01... sequence on the high byte. At the same time the low byte is updated based on the bitmap look-up table from the header file. Because I am shifting the bits RIGHT and outputting them in that order, the analyzer will show the byte in reverse order when compared to what has been defined in the header file.

  1. 0x27 is 0b00100111 in binary, and if we reverse that it becomes 0b11100100 which is 0xE4 in hexadecimal
  2. 0x45 is 0b01000101 in binary, and if we reverse that it becomes 0b10100010 which is 0xA2 in hexadecimal
  3. 0x45 is 0b01000101 in binary, and if we reverse that it becomes 0b10100010 which is 0xA2 in hexadecimal
  4. 0x45 is 0b01000101 in binary, and if we reverse that it becomes 0b10100010 which is 0xA2 in hexadecimal
  5. 0x39 is 0b00111001 in binary, and if we reverse that it becomes 0b10011100 which is 0x9C in hexadecimal

This is exactly what we capture on the logic analyzer and those written earlier are the reason behind it. Changing the shift direction will mirror the character.

Test!

image1-3.png
image1-05.png
image1-4.png
image1-8.png
image1-03.png

This is a rather simple project but as usual I didn't have much luck with everything working at the first try. Along the way I had a couple of problems with RC-5 sampling and as I mentioned before, I used my old Scanalogic 2 to align my sampling to the actual bit time of the remote control.

Apart of these timing problems there were only logic levels and connections to test, still, my Diagno logic tool proved itself quite useful for this. I used it a lot during the debug of the shift register related code segments, especially its bus mode. I will tell you more about this great tool in my next "Build an 8-bit Microcontroller" related instructable.

Cheers!